aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json57
-rw-r--r--.github/dependabot.yml12
-rw-r--r--.github/workflows/codeql-analysis.yml58
-rw-r--r--.github/workflows/node.yml6
-rw-r--r--.prettierrc.json3
-rw-r--r--.vscode/launch.json32
-rw-r--r--.vscode/settings.json20
-rw-r--r--README.md3
-rw-r--r--jest.config.js6
-rw-r--r--package.json113
-rw-r--r--src/index.ts17
-rw-r--r--src/public/css/style.css127
-rw-r--r--src/public/js/form.js128
-rw-r--r--src/routes/api.ts287
-rw-r--r--tsconfig.json186
-rw-r--r--yarn.lock29
16 files changed, 555 insertions, 529 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index ebb8e6d..ae3fde3 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,37 +1,24 @@
{
- "env": {
- "browser": true,
- "es2021": true,
- "node": true
- },
- "extends": [
- "eslint:recommended",
- "plugin:@typescript-eslint/recommended"
- ],
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "ecmaVersion": "latest",
- "sourceType": "module"
- },
- "plugins": [
- "@typescript-eslint"
- ],
- "rules": {
- "indent": [
- "error",
- 4
- ],
- "linebreak-style": [
- "error",
- "unix"
- ],
- "quotes": [
- "error",
- "single"
- ],
- "semi": [
- "error",
- "always"
- ]
- }
+ "env": {
+ "browser": true,
+ "es2021": true,
+ "node": true
+ },
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "plugins": ["@typescript-eslint", "prettier"],
+ "rules": {
+ "linebreak-style": ["error", "unix"],
+ "quotes": ["error", "single"],
+ "semi": ["error", "always"],
+ "prettier/prettier": ["error", { "singleQuote": true }]
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:prettier/recommended"
+ ]
}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 4df7644..a124ccb 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,12 +5,12 @@
version: 2
updates:
- - package-ecosystem: "npm" # See documentation for possible values
- directory: "/" # Location of package manifests
+ - package-ecosystem: 'npm' # See documentation for possible values
+ directory: '/' # Location of package manifests
schedule:
- interval: "daily"
- - package-ecosystem: "github-actions"
- directory: "/"
+ interval: 'daily'
+ - package-ecosystem: 'github-actions'
+ directory: '/'
schedule:
# Check for updates to GitHub Actions every week
- interval: "weekly"
+ interval: 'weekly'
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 2b40e64..b8c71b2 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -9,14 +9,14 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
-name: "CodeQL"
+name: 'CodeQL'
on:
push:
- branches: [ master ]
+ branches: [master]
pull_request:
# The branches below must be a subset of the branches above
- branches: [ master ]
+ branches: [master]
schedule:
- cron: '23 20 * * 2'
@@ -32,39 +32,39 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ 'javascript' ]
+ language: ['javascript']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- - name: Checkout repository
- uses: actions/checkout@v3
+ - name: Checkout repository
+ uses: actions/checkout@v3
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v1
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
- # queries: ./path/to/local/query, your-org/your-repo/queries@main
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
- # ℹī¸ Command-line programs to run using the OS shell.
- # 📚 https://git.io/JvXDl
+ # ℹī¸ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
- # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines
- # and modify them (or add more) to build your code if your project
- # uses a compiled language
+ # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
- #- run: |
- # make bootstrap
- # make release
+ #- run: |
+ # make bootstrap
+ # make release
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml
index 4c48897..366fe3b 100644
--- a/.github/workflows/node.yml
+++ b/.github/workflows/node.yml
@@ -14,8 +14,8 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
- node-version: "16.x"
- cache: "yarn"
+ node-version: '16.x'
+ cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Lint
@@ -32,4 +32,4 @@ jobs:
steps:
- uses: fastify/github-action-merge-dependabot@v3.0.2
with:
- github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..544138b
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,3 @@
+{
+ "singleQuote": true
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 3220b3d..3ae875c 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,18 +1,16 @@
{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "type": "node",
- "request": "launch",
- "name": "Launch Program",
- "program": "${workspaceFolder}/src/index.ts",
- "preLaunchTask": "npm: build",
- "outFiles": [
- "${workspaceFolder}/dist/**/*.js"
- ]
- }
- ]
-} \ No newline at end of file
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Launch Program",
+ "program": "${workspaceFolder}/src/index.ts",
+ "preLaunchTask": "npm: build",
+ "outFiles": ["${workspaceFolder}/dist/**/*.js"]
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3f638b5..82ef2a7 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,13 +1,11 @@
{
- "files.exclude": {
- "**/*.js": {
- "when": "$(basename).ts"
- },
- "**/**.js": {
- "when": "$(basename).tsx"
- }
+ "files.exclude": {
+ "**/*.js": {
+ "when": "$(basename).ts"
},
- "cSpell.words": [
- "fileupload"
- ]
-} \ No newline at end of file
+ "**/**.js": {
+ "when": "$(basename).tsx"
+ }
+ },
+ "cSpell.words": ["fileupload"]
+}
diff --git a/README.md b/README.md
index e45cb32..832ac66 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Lamperski Inverted Pendulum - Web
[![GitHub Actions](https://github.com/RosstheRoss/4951w-pendulum/actions/workflows/node.yml/badge.svg)](https://github.com/RosstheRoss/4951w-pendulum/actions/workflows/node.yml)
-[![Known Vulnerabilities](<https://snyk.io/test/github/UMN-EE4951W-Lamperski/pendulum-web/badge.svg>)](<https://snyk.io/test/github/UMN-EE4951W-Lamperski/pendulum-web>)
+[![Known Vulnerabilities](https://snyk.io/test/github/UMN-EE4951W-Lamperski/pendulum-web/badge.svg)](https://snyk.io/test/github/UMN-EE4951W-Lamperski/pendulum-web)
An complete rewrite of the web application for Professor Andrew Lamperski's Remotely Accessible Inverted Pendulum, in TypeScript.
@@ -16,6 +16,7 @@ An complete rewrite of the web application for Professor Andrew Lamperski's Remo
## Structure
The structure of the repository is as follows:
+
```
├── src (folder)
│ ├── public (folder)
diff --git a/jest.config.js b/jest.config.js
index 6193f26..e86e13b 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,5 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
- preset: 'ts-jest',
- testEnvironment: 'node',
-}; \ No newline at end of file
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+};
diff --git a/package.json b/package.json
index 74b1207..0c3368b 100644
--- a/package.json
+++ b/package.json
@@ -1,57 +1,60 @@
{
- "dependencies": {
- "cookie-parser": "^1.4.6",
- "csurf": "^1.11.0",
- "ejs": "^3.1.6",
- "express": "^4.17.3",
- "express-fileupload": "^1.3.1",
- "express-rate-limit": "^6.3.0",
- "helmet": "^5.0.2",
- "shell-quote": "^1.7.3"
- },
- "devDependencies": {
- "@types/cookie-parser": "^1.4.2",
- "@types/csurf": "^1.11.2",
- "@types/express": "^4.17.13",
- "@types/express-fileupload": "^1.2.2",
- "@types/node": "^17.0.21",
- "@types/shell-quote": "^1.7.1",
- "@typescript-eslint/eslint-plugin": "^5.14.0",
- "@typescript-eslint/parser": "^5.14.0",
- "eslint": "^8.10.0",
- "eslint-config-airbnb-base": "^15.0.0",
- "eslint-plugin-import": "^2.25.4",
- "install": "^0.13.0",
- "nodemon": "^2.0.15",
- "npm-run-all": "^4.1.5",
- "pkg": "^5.5.2",
- "typescript": "^4.6.2"
- },
- "scripts": {
- "build": "npm-run-all clean tsc copy-views",
- "build:ci": "npm-run-all clean lint tsc",
- "build:pack": "npm-run-all build pack",
- "copy-views": "cp -r ./src/views ./dist/views && cp -r ./src/public ./dist/public",
- "clean": "rm -rf dist",
- "dev": "nodemon --watch ./src -e ts,ejs,css,js --exec yarn dev:start",
- "dev:start": "npm-run-all build start",
- "lint": "eslint --ext .ts,.js ./src --fix",
- "lint:ci": "eslint --ext .ts,.js ./src",
- "pack": "pkg . -t node16-linux -o pend-server",
- "start": "node dist/index.js",
- "tsc": "tsc --project ./tsconfig.json"
- },
- "name": "4951w-pendulum-webapp",
- "version": "0.1.0",
- "description": "The webapp for Professor Lamperski's Pendulum",
- "main": "dist/index.js",
- "bin": "dist/index.js",
- "author": "Matt Strapp <matt@mattstrapp.net>",
- "repository": "https: //github.com/RosstheRoss/4951w-pendulum",
- "license": "MIT",
- "private": true,
- "pkg": {
- "scripts": "dist/**/*.js",
- "assets": "dist/**/*"
- }
+ "dependencies": {
+ "cookie-parser": "^1.4.6",
+ "csurf": "^1.11.0",
+ "ejs": "^3.1.6",
+ "express": "^4.17.3",
+ "express-fileupload": "^1.3.1",
+ "express-rate-limit": "^6.3.0",
+ "helmet": "^5.0.2",
+ "shell-quote": "^1.7.3"
+ },
+ "devDependencies": {
+ "@types/cookie-parser": "^1.4.2",
+ "@types/csurf": "^1.11.2",
+ "@types/express": "^4.17.13",
+ "@types/express-fileupload": "^1.2.2",
+ "@types/node": "^17.0.21",
+ "@types/shell-quote": "^1.7.1",
+ "@typescript-eslint/eslint-plugin": "^5.14.0",
+ "@typescript-eslint/parser": "^5.14.0",
+ "eslint": "^8.10.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-plugin-import": "^2.25.4",
+ "eslint-plugin-prettier": "^4.0.0",
+ "install": "^0.13.0",
+ "nodemon": "^2.0.15",
+ "npm-run-all": "^4.1.5",
+ "pkg": "^5.5.2",
+ "prettier": "^2.5.1",
+ "typescript": "^4.6.2"
+ },
+ "scripts": {
+ "build": "npm-run-all clean tsc copy-views",
+ "build:ci": "npm-run-all clean tsc",
+ "build:pack": "npm-run-all build pack",
+ "copy-views": "cp -r ./src/views ./dist/views && cp -r ./src/public ./dist/public",
+ "clean": "rm -rf dist",
+ "dev": "nodemon --watch ./src -e ts,ejs,css,js --exec yarn dev:start",
+ "dev:start": "npm-run-all build start",
+ "lint": "eslint --ext .ts,.js ./src --fix && prettier --write ./src",
+ "lint:ci": "eslint --ext .ts,.js ./src && prettier ./src --check",
+ "pack": "pkg . -t node16-linux -o pend-server",
+ "start": "node dist/index.js",
+ "tsc": "tsc --project ./tsconfig.json"
+ },
+ "name": "4951w-pendulum-webapp",
+ "version": "0.1.0",
+ "description": "The webapp for Professor Lamperski's Pendulum",
+ "main": "dist/index.js",
+ "bin": "dist/index.js",
+ "author": "Matt Strapp <matt@mattstrapp.net>",
+ "repository": "https: //github.com/RosstheRoss/4951w-pendulum",
+ "license": "MIT",
+ "private": true,
+ "pkg": {
+ "scripts": "dist/**/*.js",
+ "assets": "dist/**/*"
+ }
}
diff --git a/src/index.ts b/src/index.ts
index 63c803c..def7aff 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -20,14 +20,13 @@ const csrf = csurf({ cookie: true });
// Rate limiting
const rateLimiter = rateLimit({
- windowMs: 1 * 60 * 1000, // 1 minute
- max: 40, // Limit each IP to 40 requests per `window` (here, per 1 minutes)
- standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
- legacyHeaders: false, // Disable the `X-RateLimit-*` headers
+ windowMs: 1 * 60 * 1000, // 1 minute
+ max: 40, // Limit each IP to 40 requests per `window` (here, per 1 minutes)
+ standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
+ legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
app.use(rateLimiter);
-
// The API
app.use('/api/v1/', api);
@@ -40,14 +39,14 @@ app.use('/public', express.static(path.join(__dirname, 'public'))); // Set stati
/* ROUTING */
app.get('/', csrf, (req: Request, res: Response) => {
- res.render('index', { csrfToken: req.csrfToken() });
+ res.render('index', { csrfToken: req.csrfToken() });
});
app.get('/about', csrf, (req: Request, res: Response) => {
- res.render('about');
+ res.render('about');
});
// Start the server
const port = env.PORT || 2000;
app.listen(port, () => {
- console.log(`Server is listening on port ${port}`);
-}); \ No newline at end of file
+ console.log(`Server is listening on port ${port}`);
+});
diff --git a/src/public/css/style.css b/src/public/css/style.css
index a106926..3165d06 100644
--- a/src/public/css/style.css
+++ b/src/public/css/style.css
@@ -1,126 +1,117 @@
body {
- background-color: black;
- color: white;
- text-align: center;
- vertical-align: middle;
- position: relative;
+ background-color: black;
+ color: white;
+ text-align: center;
+ vertical-align: middle;
+ position: relative;
}
.header {
- margin: 5px;
- font-family: "Times New Roman", Times, serif;
+ margin: 5px;
+ font-family: 'Times New Roman', Times, serif;
}
-
/* Customize HTML paragraph element margins */
p {
- display: block;
- margin-top: 1em;
- margin-bottom: 1em;
- margin-left: 15%;
- margin-right: 15%;
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ margin-left: 15%;
+ margin-right: 15%;
}
-
/*-------------------------Buttons-----------------------------------------------------*/
-
/* Used for id=button1 */
#actuate-but {
- background-color: #0a3f73;
- border: solid;
- border-color: #d5d6d2;
- color: white;
- padding: 10px 20px;
- text-align: center;
- text-decoration: none;
- font-family: "Constantia", sans-serif;
- border-radius: 10px;
- display: inline-block;
- font-size: 20px;
+ background-color: #0a3f73;
+ border: solid;
+ border-color: #d5d6d2;
+ color: white;
+ padding: 10px 20px;
+ text-align: center;
+ text-decoration: none;
+ font-family: 'Constantia', sans-serif;
+ border-radius: 10px;
+ display: inline-block;
+ font-size: 20px;
}
-
/* Used for id=button1 - What the button looks like when a user puts one's cursor on it */
#actuate-but:hover {
- background-color: white;
- color: maroon;
- cursor: pointer;
+ background-color: white;
+ color: maroon;
+ cursor: pointer;
}
-
a {
- color: blue;
- text-decoration: none;
+ color: blue;
+ text-decoration: none;
}
-
/* Used for the about page*/
.about {
- color: white;
- text-align: center;
- margin: 5px;
- font-size: large;
+ color: white;
+ text-align: center;
+ margin: 5px;
+ font-size: large;
}
-
#Background {
- color: #ffbb00;
+ color: #ffbb00;
}
#Pendulum_info {
- /* #4294cf is similar to a light blue color */
- color: #4294cf;
+ /* #4294cf is similar to a light blue color */
+ color: #4294cf;
}
-
/* Used for id=Start */
#Start {
- font-size: 25px;
- font-weight: bold;
- color: #ffbb00;
+ font-size: 25px;
+ font-weight: bold;
+ color: #ffbb00;
}
-
.navbar {
- top: 0;
- width: 100%;
- list-style-type: none;
- margin: 0;
- padding: 0;
- overflow: hidden;
- background-color: #7a0019;
+ top: 0;
+ width: 100%;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ background-color: #7a0019;
}
.navbar li {
- float: left;
+ float: left;
}
.navbar li a {
- display: block;
- color: white;
- text-align: center;
- padding: 14px 16px;
- font-family: "Raleway", sans-serif;
- text-decoration: none;
+ display: block;
+ color: white;
+ text-align: center;
+ padding: 14px 16px;
+ font-family: 'Raleway', sans-serif;
+ text-decoration: none;
}
.navbar li a:hover {
- background-color: #ffcc33;
- color: #000000;
- /* Remove underlines from links */
- text-decoration: none;
+ background-color: #ffcc33;
+ color: #000000;
+ /* Remove underlines from links */
+ text-decoration: none;
}
h2 {
- font-size: larger;
+ font-size: larger;
}
.error {
- color: red;
-} \ No newline at end of file
+ color: red;
+}
diff --git a/src/public/js/form.js b/src/public/js/form.js
index 9e926fa..1e9eaca 100644
--- a/src/public/js/form.js
+++ b/src/public/js/form.js
@@ -1,84 +1,80 @@
// This is split into comments so it doesn't look as gross with the closure tabs
window.onload = function () {
- document.getElementById('nojs').hidden = true;
- document.getElementById('block').hidden = false;
+ document.getElementById('nojs').hidden = true;
+ document.getElementById('block').hidden = false;
};
// File submit AJAX request
// After successful upload, actuate the file by calling actuate() on the successfully uploaded file
document.getElementById('upload').onsubmit = function () {
- // Reset error message and success message
- document.getElementById('upload-err').innerText = '';
- document.getElementById('actuate-err').innerText = '';
- document.getElementById('upload-response').innerText = '';
- document.getElementById('download-link').innerText = '';
- // Make AJAX request
- let xhr = new XMLHttpRequest();
- xhr.open('POST', '/api/v1/upload');
- let formData = new FormData(this);
- xhr.send(formData);
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4) {
- let response = JSON.parse(xhr.responseText);
- if (xhr.status === 201) {
- // Display upload success message to the user
- document.getElementById('upload-response').innerText = response.msg;
- actuate(response);
- } else {
- // Display upload error message to the user
- document.getElementById('upload-err').innerText = response.error;
- // DEBUG: Print full error if unknown error occurs
- if (xhr.status === 500)
- console.error(response.error_msg);
- }
- }
- };
- document.getElementById('upload').reset();
- return false;
+ // Reset error message and success message
+ document.getElementById('upload-err').innerText = '';
+ document.getElementById('actuate-err').innerText = '';
+ document.getElementById('upload-response').innerText = '';
+ document.getElementById('download-link').innerText = '';
+ // Make AJAX request
+ let xhr = new XMLHttpRequest();
+ xhr.open('POST', '/api/v1/upload');
+ let formData = new FormData(this);
+ xhr.send(formData);
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ let response = JSON.parse(xhr.responseText);
+ if (xhr.status === 201) {
+ // Display upload success message to the user
+ document.getElementById('upload-response').innerText = response.msg;
+ actuate(response);
+ } else {
+ // Display upload error message to the user
+ document.getElementById('upload-err').innerText = response.error;
+ // DEBUG: Print full error if unknown error occurs
+ if (xhr.status === 500) console.error(response.error_msg);
+ }
+ }
+ };
+ document.getElementById('upload').reset();
+ return false;
};
// Actuate button AJAX request
// Should always be called after upload
// Implies that upload has been successful since it relies on the upload response
-//
+//
function actuate(file) {
- let xhr = new XMLHttpRequest();
- xhr.open('POST', '/api/v1/actuate');
- let data = {
- file: file.file
- };
- xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
- xhr.setRequestHeader('X-CSRF-TOKEN', file.csrf);
- xhr.send(JSON.stringify(data));
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4) {
- let response = JSON.parse(xhr.responseText);
- if (xhr.status === 200 || xhr.status === 500) {
- createDownload(response.file);
- }
- if (xhr.status === 500) {
- document.getElementById('actuate-err').innerText = response.error;
- // DEBUG: Print full error if unknown error occurs
- console.error(response.error_msg);
- }
-
- }
- return;
- };
+ let xhr = new XMLHttpRequest();
+ xhr.open('POST', '/api/v1/actuate');
+ let data = {
+ file: file.file,
+ };
+ xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
+ xhr.setRequestHeader('X-CSRF-TOKEN', file.csrf);
+ xhr.send(JSON.stringify(data));
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ let response = JSON.parse(xhr.responseText);
+ if (xhr.status === 200 || xhr.status === 500) {
+ createDownload(response.file);
+ }
+ if (xhr.status === 500) {
+ document.getElementById('actuate-err').innerText = response.error;
+ // DEBUG: Print full error if unknown error occurs
+ console.error(response.error_msg);
+ }
+ }
+ return;
+ };
}
-
// Creates the download element
function createDownload(response) {
- if (!response)
- return;
- const tempName = response.filename;
- const downloadName = response.name.split('.')[ 0 ];
- const downloadLink = document.createElement('a');
- downloadLink.setAttribute('href', `/api/v1/download/?filename=${tempName}`);
- downloadLink.setAttribute('download', `${downloadName}.csv`);
- downloadLink.innerText = 'Download CSV of results here.';
- document.getElementById('download-link').appendChild(downloadLink);
- return;
-} \ No newline at end of file
+ if (!response) return;
+ const tempName = response.filename;
+ const downloadName = response.name.split('.')[0];
+ const downloadLink = document.createElement('a');
+ downloadLink.setAttribute('href', `/api/v1/download/?filename=${tempName}`);
+ downloadLink.setAttribute('download', `${downloadName}.csv`);
+ downloadLink.innerText = 'Download CSV of results here.';
+ document.getElementById('download-link').appendChild(downloadLink);
+ return;
+}
diff --git a/src/routes/api.ts b/src/routes/api.ts
index ee80ea5..22edd79 100644
--- a/src/routes/api.ts
+++ b/src/routes/api.ts
@@ -15,15 +15,16 @@ api.use(cookieParser());
const csrf = csurf({ cookie: true });
// For file uploads
-api.use(fileUpload({
+api.use(
+ fileUpload({
preserveExtension: true, // Preserve file extension on upload
safeFileNames: true, // Only allow alphanumeric characters in file names
limits: { fileSize: 1 * 1024 * 1024 }, // Limit file size to 1MB
useTempFiles: true, // Store files in temp instead of memory
tempFileDir: '/tmp/', // Store files in /tmp/
debug: false, // Log debug information
-}));
-
+ })
+);
/*
Upload a file to the server
@@ -44,34 +45,44 @@ api.use(fileUpload({
415 when the file's MIME type is not text/x-python or text/plain (WINDOWS WHY)
500 for any other errors
*/
-api.route('/upload')
- .post(csrf, (req: Request, res: Response) => {
- try {
- // Check if anything was actually uploaded
- if (!req.files || Object.keys(req.files).length === 0)
- return res.status(400).json({ error: 'No file uploaded.' });
-
- const file: UploadedFile = req.files.file as UploadedFile; // Kludge to prevent a compiler error, only one file gets uploaded so this should be fine
-
- // Check if the file is too large (see fileUpload.limits for the limit)
- if (file.truncated)
- return res.status(413).json({ error: 'File uploaded was too large.' });
-
- // Check if the file is a python file
- if (file.mimetype !== 'text/x-python' && file.mimetype !== 'text/plain')
- return res.status(415).json({ error: 'File uploaded was not a Python file.' });
-
- res.status(201).json({ file: { file: file.name, path: file.tempFilePath }, msg: 'File uploaded successfully.', csrf: req.csrfToken() });
- } catch (err) {
- // Generic error handler
- res.status(500).json({ error: 'An unknown error occurred while uploading the file.', error_msg: err });
- }
- })
- // Fallback
- .all(csrf, (req: Request, res: Response) => {
- res.set('Allow', 'POST');
- res.status(405).json({ error: 'Method not allowed.' });
- });
+api
+ .route('/upload')
+ .post(csrf, (req: Request, res: Response) => {
+ try {
+ // Check if anything was actually uploaded
+ if (!req.files || Object.keys(req.files).length === 0)
+ return res.status(400).json({ error: 'No file uploaded.' });
+
+ const file: UploadedFile = req.files.file as UploadedFile; // Kludge to prevent a compiler error, only one file gets uploaded so this should be fine
+
+ // Check if the file is too large (see fileUpload.limits for the limit)
+ if (file.truncated)
+ return res.status(413).json({ error: 'File uploaded was too large.' });
+
+ // Check if the file is a python file
+ if (file.mimetype !== 'text/x-python' && file.mimetype !== 'text/plain')
+ return res
+ .status(415)
+ .json({ error: 'File uploaded was not a Python file.' });
+
+ res.status(201).json({
+ file: { file: file.name, path: file.tempFilePath },
+ msg: 'File uploaded successfully.',
+ csrf: req.csrfToken(),
+ });
+ } catch (err) {
+ // Generic error handler
+ res.status(500).json({
+ error: 'An unknown error occurred while uploading the file.',
+ error_msg: err,
+ });
+ }
+ })
+ // Fallback
+ .all(csrf, (req: Request, res: Response) => {
+ res.set('Allow', 'POST');
+ res.status(405).json({ error: 'Method not allowed.' });
+ });
/*
Actuate the pendulum
@@ -102,20 +113,20 @@ api.route('/upload')
Minimizing PE vectors like running this as an extremely low privilege user is a must.
*/
-api.route('/actuate')
- // Snyk error mitigation, should be fine since the rate limiting is already in place
- // file deepcode ignore NoRateLimitingForExpensiveWebOperation: This is already rate limited by the website, so we don't need to do it again
- .post(csrf, async (req: Request, res: Response) => {
- try {
- const path: string = req.body.file.path;
- // Verify that the file exists and is a regular file
- // Return if not since the res will be sent by the verifyFile function
- if (await verifyFile(path, res) !== true)
- return;
-
- const escaped = quote([ path ]);
- // Run the code
- /*
+api
+ .route('/actuate')
+ // Snyk error mitigation, should be fine since the rate limiting is already in place
+ // file deepcode ignore NoRateLimitingForExpensiveWebOperation: This is already rate limited by the website, so we don't need to do it again
+ .post(csrf, async (req: Request, res: Response) => {
+ try {
+ const path: string = req.body.file.path;
+ // Verify that the file exists and is a regular file
+ // Return if not since the res will be sent by the verifyFile function
+ if ((await verifyFile(path, res)) !== true) return;
+
+ const escaped = quote([path]);
+ // Run the code
+ /*
TODO:
- Potentially add the limiter to one-per-person here
- Add a timeout
@@ -123,37 +134,48 @@ api.route('/actuate')
- Make this more secure
- HOW?
*/
- // let output = '';
- let stderr = '';
- const actuation = spawn('python', escaped.split(' '));
- // actuation.stdout.on('data', (data: Buffer) => {
- // output += data.toString();
- // });
- actuation.stderr.on('data', (data: Buffer) => {
- stderr += `STDERR: ${data.toString()}`;
- });
- actuation.on('close', (code: number) => {
- const filename: string = (req.body.file.path as string).split('/').pop() as string;
- // Make sure the program exited with a code of 0 (success)
- if (code !== 0)
- return res.status(500).json({ error: `Program exited with exit code ${code}`, error_msg: stderr, file: { name: req.body.file.file, filename: filename } });
- return res.status(200).json({ file: { name: req.body.file.file, filename: filename } });
- });
- // Kill the process if it takes too long
- // Default timeout is 120 seconds (2 minutes)
- setTimeout(() => {
- actuation.kill();
- }, 120000);
- } catch (err) {
- // Generic error handler
- return res.status(500).json({ error: 'An unknown error occurred while running the file.', error_msg: err });
- }
- })
- // Fallback
- .all(csrf, (req: Request, res: Response) => {
- res.set('Allow', 'POST');
- return res.status(405).json({ error: 'Method not allowed.' });
- });
+ // let output = '';
+ let stderr = '';
+ const actuation = spawn('python', escaped.split(' '));
+ // actuation.stdout.on('data', (data: Buffer) => {
+ // output += data.toString();
+ // });
+ actuation.stderr.on('data', (data: Buffer) => {
+ stderr += `STDERR: ${data.toString()}`;
+ });
+ actuation.on('close', (code: number) => {
+ const filename: string = (req.body.file.path as string)
+ .split('/')
+ .pop() as string;
+ // Make sure the program exited with a code of 0 (success)
+ if (code !== 0)
+ return res.status(500).json({
+ error: `Program exited with exit code ${code}`,
+ error_msg: stderr,
+ file: { name: req.body.file.file, filename: filename },
+ });
+ return res
+ .status(200)
+ .json({ file: { name: req.body.file.file, filename: filename } });
+ });
+ // Kill the process if it takes too long
+ // Default timeout is 120 seconds (2 minutes)
+ setTimeout(() => {
+ actuation.kill();
+ }, 120000);
+ } catch (err) {
+ // Generic error handler
+ return res.status(500).json({
+ error: 'An unknown error occurred while running the file.',
+ error_msg: err,
+ });
+ }
+ })
+ // Fallback
+ .all(csrf, (req: Request, res: Response) => {
+ res.set('Allow', 'POST');
+ return res.status(405).json({ error: 'Method not allowed.' });
+ });
/*
Download the CSV file after running the pendulum
@@ -166,35 +188,34 @@ api.route('/actuate')
404 when the file is not accessible or does not exist
500 for any other errors
*/
-api.route('/download')
- .get(csrf, async (req: Request, res: Response) => {
- const filename: string = req.query.filename as string;
- if (!filename)
- return res.status(400).json({ error: 'No filename specified.' });
- // Make sure no path traversal is attempted
- // This regex matches all alphanumeric characters, underscores, and dashes.
- // MAKE SURE THIS DOES NOT ALLOW PATH TRAVERSAL
- if (!/^[\w-]+$/.test(filename))
- return res.status(403).json({ error: 'No.' });
-
- const path = `/tmp/${filename}.csv`;
-
- // Verify that the file exists and is a regular file
- // Return if not since the res will be sent by the verifyFile function
- if (await verifyFile(path, res) !== true)
- return;
- // Read the file and send it to the client
- res.type('text/csv');
- // Snyk error mitigation, should be fine since tmp is private and the simple regex above should prevent path traversal
- // deepcode ignore PT: This is probably mitigated by the regex
- return res.sendFile(path);
- })
- // Fallback
- .all(csrf, (req: Request, res: Response) => {
- res.set('Allow', 'GET');
- return res.status(405).json({ error: 'Method not allowed.' });
- });
+api
+ .route('/download')
+ .get(csrf, async (req: Request, res: Response) => {
+ const filename: string = req.query.filename as string;
+ if (!filename)
+ return res.status(400).json({ error: 'No filename specified.' });
+ // Make sure no path traversal is attempted
+ // This regex matches all alphanumeric characters, underscores, and dashes.
+ // MAKE SURE THIS DOES NOT ALLOW PATH TRAVERSAL
+ if (!/^[\w-]+$/.test(filename))
+ return res.status(403).json({ error: 'No.' });
+
+ const path = `/tmp/${filename}.csv`;
+ // Verify that the file exists and is a regular file
+ // Return if not since the res will be sent by the verifyFile function
+ if ((await verifyFile(path, res)) !== true) return;
+ // Read the file and send it to the client
+ res.type('text/csv');
+ // Snyk error mitigation, should be fine since tmp is private and the simple regex above should prevent path traversal
+ // deepcode ignore PT: This is probably mitigated by the regex
+ return res.sendFile(path);
+ })
+ // Fallback
+ .all(csrf, (req: Request, res: Response) => {
+ res.set('Allow', 'GET');
+ return res.status(405).json({ error: 'Method not allowed.' });
+ });
/*
Verify that the file exists and is a regular file
@@ -207,36 +228,38 @@ api.route('/download')
** AFTER THIS POINT, THE API HAS ALREADY SENT A RESPONSE, SO THE FUNCTION THAT CALLED IT SHOULD NOT RETURN ANOTHER RESPONSE **
*/
async function verifyFile(file: string, res: Response) {
- // Make sure the file being requested to run exists
- try {
- await access(file);
- } catch (err) {
- res.status(404).json({ error: 'File is not accessible or does not exist.' });
- return false;
- }
- // This is a try catch because otherwise type checking will fail and get all messed up
- // Handle your promise rejections, kids
- try {
- const stats = await stat(file);
- // Make sure the file being requested to run is a regular file
- if (!stats.isFile()) {
- res.status(400).json({ error: 'File is not a regular file.' });
- return false;
- }
- // Make sure the file being requested to run is not a directory
- else if (stats.isDirectory()) {
- res.status(400).json({ error: 'File is a directory.' });
- return false;
- }
-
-
- // File does exist and is a regular file, so it is good to go
- return true;
+ // Make sure the file being requested to run exists
+ try {
+ await access(file);
+ } catch (err) {
+ res
+ .status(404)
+ .json({ error: 'File is not accessible or does not exist.' });
+ return false;
+ }
+ // This is a try catch because otherwise type checking will fail and get all messed up
+ // Handle your promise rejections, kids
+ try {
+ const stats = await stat(file);
+ // Make sure the file being requested to run is a regular file
+ if (!stats.isFile()) {
+ res.status(400).json({ error: 'File is not a regular file.' });
+ return false;
}
- catch (err) {
- res.status(404).json({ error: 'File is not accessible or does not exist.' });
- return false;
+ // Make sure the file being requested to run is not a directory
+ else if (stats.isDirectory()) {
+ res.status(400).json({ error: 'File is a directory.' });
+ return false;
}
+
+ // File does exist and is a regular file, so it is good to go
+ return true;
+ } catch (err) {
+ res
+ .status(404)
+ .json({ error: 'File is not accessible or does not exist.' });
+ return false;
+ }
}
export default api;
diff --git a/tsconfig.json b/tsconfig.json
index 4ee1d0f..006f26d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,96 +1,94 @@
{
- "compilerOptions": {
- /* Visit https://aka.ms/tsconfig.json to read more about this file */
- /* Projects */
- // "incremental": true, /* Enable incremental compilation */
- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
- // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
- /* Language and Environment */
- "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
- // "jsx": "preserve", /* Specify what JSX code is generated. */
- // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
- // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
- // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
- // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
- // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
- // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
- // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
- // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
- /* Modules */
- "module": "commonjs", /* Specify what module code is generated. */
- // "rootDir": "./", /* Specify the root folder within your source files. */
- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
- // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
- // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
- // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
- // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
- // "types": [], /* Specify type package names to be included without being referenced in a source file. */
- // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
- // "resolveJsonModule": true, /* Enable importing .json files */
- // "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
- /* JavaScript Support */
- // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
- // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
- // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
- /* Emit */
- "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
- "declarationMap": true, /* Create sourcemaps for d.ts files. */
- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- "sourceMap": true, /* Create source map files for emitted JavaScript files. */
- // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
- "outDir": "./dist", /* Specify an output folder for all emitted files. */
- "removeComments": true, /* Disable emitting comments. */
- // "noEmit": true, /* Disable emitting files from a compilation. */
- "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
- // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
- // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
- // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
- // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
- // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
- // "newLine": "crlf", /* Set the newline character for emitting files. */
- // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
- // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
- // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
- // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
- // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
- // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
- /* Interop Constraints */
- // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
- // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
- // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
- /* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
- "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
- // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
- // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
- // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
- // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
- // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
- // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
- // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
- // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
- // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
- // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
- // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
- // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
- // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
- // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
- // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
- // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
- /* Completeness */
- // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
- },
- "include": [
- "src/**/*"
- ]
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
+ /* Projects */
+ // "incremental": true, /* Enable incremental compilation */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+ /* Language and Environment */
+ "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
+ // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ /* Modules */
+ "module": "commonjs" /* Specify what module code is generated. */,
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "resolveJsonModule": true, /* Enable importing .json files */
+ // "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
+ /* Emit */
+ "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
+ "declarationMap": true /* Create sourcemaps for d.ts files. */,
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ "sourceMap": true /* Create source map files for emitted JavaScript files. */,
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./dist" /* Specify an output folder for all emitted files. */,
+ "removeComments": true /* Disable emitting comments. */,
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ "importHelpers": true /* Allow importing helper functions from tslib once per project, instead of including them per-file. */,
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+ /* Type Checking */
+ "strict": true /* Enable all strict type-checking options. */,
+ "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
+ // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
+ // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ },
+ "include": ["src/**/*"]
}
diff --git a/yarn.lock b/yarn.lock
index d9f9244..8c18ba4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -968,6 +968,11 @@ eslint-config-airbnb-base@^15.0.0:
object.entries "^1.1.5"
semver "^6.3.0"
+eslint-config-prettier@^8.5.0:
+ version "8.5.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1"
+ integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==
+
eslint-import-resolver-node@^0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
@@ -1003,6 +1008,13 @@ eslint-plugin-import@^2.25.4:
resolve "^1.20.0"
tsconfig-paths "^3.12.0"
+eslint-plugin-prettier@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0"
+ integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==
+ dependencies:
+ prettier-linter-helpers "^1.0.0"
+
eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
@@ -1183,6 +1195,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+fast-diff@^1.1.2:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
+ integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+
fast-glob@^3.2.9:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
@@ -2405,6 +2422,18 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
+prettier-linter-helpers@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
+ integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
+ dependencies:
+ fast-diff "^1.1.2"
+
+prettier@^2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
+ integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
+
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"