0
Fork 0
mirror of https://codeberg.org/SafeTwitch/safetwitch.git synced 2024-12-22 05:12:57 -05:00

Initial commit

This commit is contained in:
dragongoose 2023-03-07 01:19:05 -05:00
parent e7dc781620
commit bc9336468b
33 changed files with 12345 additions and 0 deletions

3
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

15
frontend/.eslintrc.cjs Normal file
View file

@ -0,0 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

28
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

46
frontend/README.md Normal file
View file

@ -0,0 +1,46 @@
# test
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

1
frontend/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

13
frontend/index.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body class="bg-ctp-base">
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

7987
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

41
frontend/package.json Normal file
View file

@ -0,0 +1,41 @@
{
"name": "test",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@dragongoose/streamlink": "^1.0.2",
"@videojs-player/vue": "^1.0.0",
"oh-vue-icons": "^1.0.0-rc3",
"video.js": "^8.0.4",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@catppuccin/tailwindcss": "^0.1.1",
"@rushstack/eslint-patch": "^1.2.0",
"@types/node": "^18.14.2",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.2",
"@vue/tsconfig": "^0.1.3",
"autoprefixer": "^10.4.13",
"eslint": "^8.34.0",
"eslint-plugin-vue": "^9.9.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"prettier": "^2.8.4",
"tailwindcss": "^3.2.7",
"typescript": "~4.8.4",
"vite": "^4.1.4",
"vue-tsc": "^1.2.0"
}
}

View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

12
frontend/src/App.vue Normal file
View file

@ -0,0 +1,12 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
import NavbarItem from './components/Navbar.vue'
</script>
<template>
<navbar-item></navbar-item>
<Suspense>
<RouterView />
</Suspense>
</template>

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1,26 @@
<script lang="ts">
export default {};
import { RouterLink } from 'vue-router'
</script>
<template>
<div class="p-4 flex items-center justify-between bg-ctp-base text-white">
<h1 class="font-bold text-2xl">Naqvbar</h1>
<div>
<form class="relative">
<label for="searchBar" class="hidden">Search</label>
<v-icon name="io-search-outline" class="text-black absolute my-auto inset-y-0 left-2"></v-icon>
<input type="text" id="searchBar" name="searchBar" placeholder="Search" class="rounded-md p-1 pl-8 text-black">
</form>
</div>
<ul class="inline-flex space-x-6 font-medium">
<router-link to="">Github</router-link>
<router-link to="/preferences">Preferences</router-link>
<router-link to="/about">About</router-link>
</ul>
</div>
</template>

23
frontend/src/main.ts Normal file
View file

@ -0,0 +1,23 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './assets/index.css'
import VueVideoPlayer from '@videojs-player/vue'
import 'video.js/dist/video-js.css'
const app = createApp(App)
app.use(VueVideoPlayer)
import { OhVueIcon, addIcons } from "oh-vue-icons";
import { IoSearchOutline, IoLink, FaCircleNotch, BiTwitter, BiInstagram, BiDiscord, BiYoutube, BiTiktok } from "oh-vue-icons/icons";
addIcons(IoSearchOutline, IoLink, FaCircleNotch, BiTwitter, BiInstagram, BiDiscord, BiYoutube, BiTiktok)
app.component("v-icon", OhVueIcon);
app.use(router)
app.mount('#app')

View file

@ -0,0 +1,22 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import UserView from '../views/UserView.vue'
import PageNotFound from '../views/PageNotFound.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/:username',
component: UserView
},
{ path: '/:pathMatch(.*)*', component: PageNotFound}
]
})
export default router

View file

@ -0,0 +1,7 @@
<script setup lang="ts">
</script>
<template>
</template>

View file

@ -0,0 +1,11 @@
<script lang="ts">
export default {};
</script>
<template>
<div class="flex flex-col items-center pt-10 font-bold text-5xl text-white">
<h1>oops....</h1>
<h1>this page wasn't found()</h1>
<h2 class="text-4xl">maybe go <RouterLink to="/" class="text-gray-500">home</RouterLink>?</h2>
</div>
</template>

View file

@ -0,0 +1,122 @@
<script lang="ts" >
import { ref, onMounted } from 'vue'
import { useRoute } from "vue-router";
import type { StreamerData } from '../../../server/routes/profile/profileRoute'
import { VideoPlayer } from '@videojs-player/vue'
import 'video.js/dist/video-js.css'
export default {
async setup() {
const route = useRoute()
const username = route.params.username
const getUser = async () => {
const res = await fetch(`http://localhost:7000/api/users/${username}`)
if (res.status !== 200) {
return;
}
const data: StreamerData = await res.json()
data.pfp = `http://localhost:7000/proxy/img?imageUrl=${encodeURIComponent(data.pfp)}`
return data
}
const data = ref()
onMounted(async () => {
const fetchedUser = await getUser()
data.value = fetchedUser
})
return {
data
}
},
components: {
VideoPlayer
},
methods: {
truncate(value: string, length: number) {
if (value.length > length) {
return value.substring(0, length) + "...";
} else {
return value;
}
}
}
}
</script>
<template>
<div v-if="!data" id="loadingDiv" class="flex mx-auto justify-center bg-ctp-crust rounded-lg w-2/3 p-2 text-white">
<div class="flex space-x-3">
<h1 class="text-4xl font-bold"> Searching... </h1>
<v-icon name="fa-circle-notch" class="animate-spin w-10 h-10"></v-icon>
</div>
</div>
<div v-else class="flex flex-wrap mx-auto bg-ctp-crust p-6 rounded-lg max-w-prose text-white">
<div v-if="data.isLive" class="w-full mx-auto rounded-lg mb-5">
<video-player
src="https://video-weaver.jfk04.hls.ttvnw.net/v1/playlist/CpwF74ZwzfoheHjVAIg7VWZzSnB9W3FgXzezhTA3ye7QTA9aJjprgctuMZwlTRQ1PUqoltrgVBu7SJ3vJfALlIkNYAhuYjZMnFaFnEqzv-jIH0gXW-AWgu3f-vDbarfun78h4vUHecELAqyJM_c6IEDM2b5uHCE1ZfSnZnk2ZV8XHyzbwUz8Uc-e1YM1DyCpGYHrtz_Z-0TR-ordiDzHJ8lFCzn8F8zbbePoFMD2kex4f_6neLyuAfbh2jLZj6JNPt6o63fP2N9WiHxlRT75ACUihbBbI2llL-UCdpA_lXyTWFUud28R2wJGCqt8WUm-wjECnS8BINcThhPEnN_I1imGFN-CwqxqeSMpFkzA05_Q4HoP9Bu-Z4ln2UUw7ljy9OZeYcR0rM29rmKNCa5VPA4mzuAM_2al7JLiabC9t2rJvYZgklWgFaxc6UoChOU5yQsgpac_MNNNz7wIWqgjHSRL-AI4XQDIAHWZwm7GeEB5KJhxvI_8dgnnui7t9MBFU8A2h73iScGGZOmYfK3jQrIG0-kEhjBMCopu4XPv0RxSbSD9SvncTUmNer6ybUKkjXC793AC7xREE1z6xEGQfT5wbTbC2WscKQn883ShGD5XtfW5rZIenitxvNFFgM3Ttwv5FeSY0o8angPRPzexZ5fijOZ2eAmsXgrW_pN380ba4qpbrsaalJcdtysN9Da-QmCWbsjnoKMkQ_-12PwwpDYAIyWjmeW3JrvVMzClK_WV7Z70pEt6yl_9W4Qf0zt4mEyF8ghG4nxPZutSCt4HPilKLzPCLYfcuEiIlW6hZ9riNTP7jf0Yp-afDE89lYGYrj0xNh4C8LahZTNtGWKVkxRxw8w_v2lhBuvmrNJrwjs2Rwyw_EZANqi0_CnvWEMaDKw-7_jk6XNkCi0NtyABKgl1cy1lYXN0LTIwgQY.m3u8"
poster="/your-path/poster.jpg" controls :loop="true" :volume="0.6" :autoplay="'muted'" :fluid="true" />
</div>
<div class="inline-flex justify-between">
<div class="flex">
<div class="container w-28 h-28 relative">
<img :src="data.pfp" class="rounded-full border-4 border-ctp-teal p-0.5 w-auto h-28">
<span class="absolute bottom-0 right-[1.8rem] bg-ctp-red font-bold p-2.5 py-0.5 rounded-md">LIVE</span>
</div>
<div class="ml-3">
<h1 class="text-4xl font-bold mb-3">{{ data.username }}</h1>
<h1 v-if="!data.stream" class="font-bold text-md self-end">{{ data.followersAbbv }} Followers</h1>
<div v-else class="w-[12rem]">
<p class="text-md font-bold self-end"> {{ truncate(data.stream.title, 200) }} </p>
</div>
</div>
</div>
<div class="pt-5 pr-5 flex rounded-lg">
<span v-if="!data.isLive"
class=" font-bold text-sm bg-ctp-mantle border border-ctp-red p-3.5 rounded-lg">OFFLINE</span>
<div v-else class="justify-end">
<ul class="flex font-bold flex-wrap text-sm justify-end float-right max-h-24 overflow-y-auto">
<li v-for="tag in data.stream.tags" class="p-2.5 py-1 m-0.5 bg-ctp-mantle rounded-md inline-flex">
{{ tag }}
</li>
</ul>
</div>
</div>
</div>
<div class="bg-ctp-mantle m-5 p-5 pt-3 rounded-lg w-full space-y-3">
<div class="inline-flex w-full">
<span class="pr-3 font-bold text-3xl">About</span>
</div>
<p class="mb-5">{{ data.about }}</p>
<hr class="my-auto w-full bg-gray-200 rounded-full opacity-40" />
<ul class="flex font-semibold text-md justify-start flex-wrap flex-row">
<li v-for="link in data.socials">
<a :href="link.link" class="text-white hover:text-gray-400 mr-4">
<v-icon :name="link.type ? `bi-${link.type}` : 'io-link'" class="w-6 h-6 mr-1"></v-icon>
<span>{{ link.text }}</span>
</a>
</li>
</ul>
</div>
</div>
</template>

View file

@ -0,0 +1,15 @@
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx,vue}",
],
theme: {
extend: {},
},
plugins: [
require('@catppuccin/tailwindcss')({
prefix: 'ctp',
defaultFlavour: 'mocha',
})
],
}

16
frontend/tsconfig.json Normal file
View file

@ -0,0 +1,16 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

View file

@ -0,0 +1,8 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}

14
frontend/vite.config.ts Normal file
View file

@ -0,0 +1,14 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

6
package.json Normal file
View file

@ -0,0 +1,6 @@
{
"scripts": {
"build": "cd frontend && npm run build",
"prod": "cd server && npm run prod"
}
}

1
server/.env Normal file
View file

@ -0,0 +1 @@
PORT=7000

36
server/index.ts Normal file
View file

@ -0,0 +1,36 @@
import express, { Express, NextFunction, Request, Response } from 'express';
import dotenv from 'dotenv'
import history from 'connect-history-api-fallback'
import routes from './routes'
dotenv.config()
const app: Express = express();
const port = process.env.PORT
app.use(routes)
app.use(history())
app.use(express.static('../frontend/dist'))
// 404 handler
app.use((req, res) => {
if (!res.headersSent) {
res.status(404).send('404')
}
});
// handle errors
app.use(errorHandler)
function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
if (res.headersSent) {
return next(err)
}
res.status(500)
res.send('error')
console.log(err)
}
app.listen(port, () => {
console.log('Server up')
})

3491
server/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

21
server/package.json Normal file
View file

@ -0,0 +1,21 @@
{
"dependencies": {
"@dragongoose/streamlink": "^1.0.3",
"connect-history-api-fallback": "^2.0.0",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"puppeteer": "^19.7.2"
},
"devDependencies": {
"@types/connect-history-api-fallback": "^1.3.5",
"@types/express": "^4.17.17",
"@types/node": "^18.14.6",
"nodemon": "^2.0.21",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
},
"scripts": {
"dev": "npx nodemon index.ts",
"prod": "npx ts-node index.ts"
}
}

10
server/routes.ts Normal file
View file

@ -0,0 +1,10 @@
import { Router } from 'express';
import profileRoute from './routes/profile/profileRoute'
import proxyRoute from './routes/proxy/proxyRoute'
const routes = Router();
routes.use('/api', profileRoute)
routes.use('/proxy', proxyRoute)
export default routes

View file

@ -0,0 +1,220 @@
import { Router } from 'express'
import puppeteer, { Browser, Page } from 'puppeteer'
import { LooseObject } from '../../types/looseTypes'
import { Streamlink } from '@dragongoose/streamlink'
const profileRouter = Router()
export interface Socials {
type: string | null
text: string,
link: string
}
export interface StreamData {
tags: string[]
title: string
topic: string
startedAt: number
qualities: string[]
}
export interface StreamerData {
username: string,
followers: number,
followersAbbv: string,
isLive: boolean,
about: string,
socials?: string[],
pfp: string;
stream?: StreamData
}
const abbreviatedNumberToNumber = (num: string) => {
const base = parseFloat(num)
const matches: {[k: string]: number} = {
'k': 1000,
'm': 1000000,
'b': 1000000000
}
const abbreviation: string = num.charAt(num.length - 1).toLowerCase()
if(matches[abbreviation]) {
const numberOnly: number = Number(num.slice(0, -1))
return numberOnly * matches[abbreviation]
} else {
return null
}
}
// https:// advancedweb.hu/how-to-speed-up-puppeteer-scraping-with-parallelization/
const withBrowser = async (fn: Function) => {
const browser = await puppeteer.launch({
headless: false,
args: ['--no-sandbox']
});
try {
return await fn(browser);
} finally {
await browser.close();
}
}
const withPage = (browser: Browser) => async (fn: Function) => {
const page = await browser.newPage();
//turns request interceptor on
await page.setRequestInterception(true);
//if the page makes a request to a resource type of image or stylesheet then abort that request
page.on('request', request => {
if (request.resourceType() === 'image')
request.abort();
else
request.continue();
});
try {
return await fn(page);
} finally {
await page.close();
}
}
let isLive: boolean
const getStreamData = async (page: Page) => {
const streamData: LooseObject = {}
if(!isLive) return null
// Get stream tags
const tagsSelector = '.eUxEWt * span'
const tags: string[] = await page.$$eval(tagsSelector, elements => elements.map(el => el.innerHTML))
streamData.tags = tags
// Get stream title
const titleSelector = 'h2.CoreText-sc-1txzju1-0'
const title: string = await page.$eval(titleSelector, element => element.innerText)
streamData.title = title
// Get topic
const topicSelector = '.hfMGmo'
const topic = await page.$eval(topicSelector, element => element.textContent)
streamData.topic = topic
// Get Start time
const liveTimeSelector = '.live-time'
// formated as HH:MM:SS
const liveTime = await page.$eval(liveTimeSelector, element => element.textContent)
if(!liveTime) return
const liveTimeSplit: number[] = liveTime.split(':').map(Number)
let date = new Date()
let { hours, minutes, seconds } = { hours: date.getHours(), minutes: date.getMinutes(), seconds: date.getSeconds()}
// Subtracts current live time from current
// date to get the time the stream started
date.setHours(hours - liveTimeSplit[0])
date.setMinutes(minutes - liveTimeSplit[1])
date.setSeconds(seconds - liveTimeSplit[2])
streamData.startedAt = date.getTime()
return streamData as StreamData
}
const getAboutData = async (page: Page) => {
const aboutData: LooseObject = {}
if (!isLive) {
// Get data from about page
const aboutPageButtonSelector = 'li.InjectLayout-sc-1i43xsx-0:nth-child(2) > a:nth-child(1) > div:nth-child(1) > div:nth-child(1) > p:nth-child(1)'
await page.click(aboutPageButtonSelector)
}
await page.waitForSelector('.ccXeNc')
const followersSelector = '.kuAEke'
const followers = await page.$eval(followersSelector, element => element.innerHTML)
aboutData.followersAbbv = followers
aboutData.followers = abbreviatedNumberToNumber(followers)
const aboutSectionSelector = '.kLFSJC'
const aboutSection = await page.$eval(aboutSectionSelector, element => element.innerHTML)
aboutData.about = aboutSection
const socialSelector = '.ccXeNc * a'
const socials: Socials[] = await page.$$eval(socialSelector, elements => elements.map((el) => {
const getHostName = (url: string) => {
const match = url.match(/:\/\/(www[0-9]?\.)?(.[^/:]+)/i);
if (match != null && match.length > 2 && typeof match[2] === 'string' && match[2].length > 0) {
const hostname = match[2].split(".");
return hostname[0];
}
else {
return null;
}
}
const validHosts = ['instagram', 'youtube', 'discord', 'tiktok','twitter']
const socialHost = getHostName(el.href) || el.href || ''
let type: string | null = socialHost
if(!validHosts.includes(socialHost))
type = null
return {
type,
link: el.href,
text: el.innerText
}
}))
aboutData.socials = socials
const profilePictureSelector = 'figure.ScAvatar-sc-144b42z-0:nth-child(2) > img:nth-child(1)'
const profilePicutre = await page.$eval(profilePictureSelector, element => element.getAttribute('src'))
aboutData.pfp = profilePicutre
return aboutData as StreamerData
}
const getStreamerData = async (username: string) => {
let recoveredData: LooseObject = {}
await withBrowser(async (browser: Browser) => {
const result = await withPage(browser)(async (page: Page) => {
await page.goto(`https://twitch.tv/${username}`)
return Promise.all([getStreamData(page), getAboutData(page)])
})
recoveredData = result[1]
recoveredData.stream = result[0]
if(result[0] !== null) recoveredData.isLive = true
await browser.close()
})
recoveredData.username = username
return recoveredData as StreamerData
}
profileRouter.get('/users/:username', async (req, res) => {
const username = req.params.username
const streamlink = new Streamlink(`https://twitch.tv/${username}`, {})
isLive = await streamlink.isLive()
const qualities = await streamlink.getQualities()
let streamerData = await getStreamerData(username)
if(streamerData.stream)
streamerData.stream.qualities = qualities
res.send(streamerData)
})
export default profileRouter

View file

@ -0,0 +1,27 @@
import { Router, Response, Request, NextFunction } from 'express'
const proxyRouter = Router();
proxyRouter.get('/img', async (req: Request, res: Response, next: NextFunction) => {
const imageUrl = req.query.imageUrl?.toString()
if(!imageUrl) return;
fetch(imageUrl).then((response) => {
response.body!.pipeTo(
new WritableStream({
start() {
response.headers.forEach((v, n) => res.setHeader(n, v));
},
write(chunk) {
res.write(chunk);
},
close() {
res.end();
},
})
);
})
.catch((err) => next(err))
})
export default proxyRouter

103
server/tsconfig.json Normal file
View file

@ -0,0 +1,103 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "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. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* 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. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "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": "./", /* 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, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when 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, /* Add 'undefined' to a type when accessed using an index. */
// "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. */
}
}

View file

@ -0,0 +1,3 @@
export interface LooseObject {
[key: string]: any
}