mirror of
https://github.com/pavlobu/deskreen.git
synced 2025-05-16 07:20:16 -07:00
added darkwire.io server and app/client
add client test works
This commit is contained in:
parent
86a108c1ea
commit
a3996c7a76
@ -54,3 +54,7 @@ package.json
|
||||
*.css.d.ts
|
||||
*.sass.d.ts
|
||||
*.scss.d.ts
|
||||
|
||||
|
||||
# Entire app/client directory
|
||||
app/client/*
|
||||
|
12
.github/workflows/codecov.yml
vendored
12
.github/workflows/codecov.yml
vendored
@ -1,5 +1,5 @@
|
||||
name: codecov generate
|
||||
on: [push, pull_request]
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-18.04
|
||||
@ -13,14 +13,16 @@ jobs:
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Configure private AWS npm registry
|
||||
- name: yarn install from npmjs registry
|
||||
run: |
|
||||
yarn install --no-lockfile
|
||||
|
||||
- name: Configure private AWS npm registry and install packages from it
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
npm config set registry https://packages.deskreen.com/
|
||||
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"
|
||||
npm config set always-auth true
|
||||
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
|
||||
- name: yarn build
|
||||
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@ -46,14 +46,16 @@ jobs:
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Configure private AWS npm registry
|
||||
- name: yarn install from npmjs registry
|
||||
run: |
|
||||
yarn install --no-lockfile
|
||||
|
||||
- name: Configure private AWS npm registry and install packages from it
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
npm config set registry https://packages.deskreen.com/
|
||||
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"
|
||||
npm config set always-auth true
|
||||
|
||||
- name: yarn install --frozen-lockfile
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
|
||||
- name: yarn package-ci
|
||||
|
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: build and test
|
||||
|
||||
on: [push, pull_request]
|
||||
on: [pull_request]
|
||||
|
||||
# NPMRC_ADMIN_USER_AUTH_TOKEN
|
||||
|
||||
@ -21,14 +21,16 @@ jobs:
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Configure private AWS npm registry
|
||||
- name: yarn install from npmjs registry
|
||||
run: |
|
||||
yarn install --no-lockfile
|
||||
|
||||
- name: Configure private AWS npm registry and install packages from it
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
npm config set registry https://packages.deskreen.com/
|
||||
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"
|
||||
npm config set always-auth true
|
||||
|
||||
- name: yarn install --frozen-lockfile
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
|
||||
# following step does code signing when `electron-builder --publish always` (look in package.json)
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,9 @@
|
||||
test-reports/
|
||||
test-reporter.xml
|
||||
|
||||
app/client/node_modules
|
||||
app/client/build
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
@ -6,8 +6,8 @@ import App from './containers/App';
|
||||
import HomePage from './containers/HomePage';
|
||||
|
||||
// Lazily load routes and code split with webpacck
|
||||
const LazyCounterPage = React.lazy(() =>
|
||||
import(/* webpackChunkName: "CounterPage" */ './containers/CounterPage')
|
||||
const LazyCounterPage = React.lazy(
|
||||
() => import(/* webpackChunkName: "CounterPage" */ './containers/CounterPage')
|
||||
);
|
||||
|
||||
const CounterPage = (props: Record<string, any>) => (
|
||||
|
16
app/api/config.ts
Normal file
16
app/api/config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/* istanbul ignore file */
|
||||
let host;
|
||||
let protocol;
|
||||
let port;
|
||||
|
||||
if (!host && !protocol && !port) {
|
||||
host = 'localhost';
|
||||
protocol = 'http';
|
||||
port = 3131; // TODO: read port from signaling server api
|
||||
}
|
||||
|
||||
export default {
|
||||
host,
|
||||
port,
|
||||
protocol,
|
||||
};
|
14
app/api/generator.ts
Normal file
14
app/api/generator.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/* istanbul ignore file */
|
||||
import config from './config';
|
||||
|
||||
export default (resourceName = '') => {
|
||||
const { port, protocol, host } = config;
|
||||
|
||||
const resourcePath = resourceName;
|
||||
|
||||
if (!host) {
|
||||
return `/localhost`;
|
||||
}
|
||||
|
||||
return `${protocol}://${host}:${port}/${resourcePath}`;
|
||||
};
|
23
app/client/.gitignore
vendored
Normal file
23
app/client/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
44
app/client/README.md
Normal file
44
app/client/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.<br />
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br />
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br />
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br />
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br />
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
40
app/client/package.json
Normal file
40
app/client/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "deskreen-client-viewer",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.3",
|
||||
"typescript": "~3.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"test:nowatch": "react-scripts test --watchAll=false"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
BIN
app/client/public/favicon.ico
Normal file
BIN
app/client/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
43
app/client/public/index.html
Normal file
43
app/client/public/index.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
BIN
app/client/public/logo192.png
Normal file
BIN
app/client/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
app/client/public/logo512.png
Normal file
BIN
app/client/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
app/client/public/manifest.json
Normal file
25
app/client/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
app/client/public/robots.txt
Normal file
3
app/client/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
38
app/client/src/App.css
Normal file
38
app/client/src/App.css
Normal file
@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
9
app/client/src/App.test.tsx
Normal file
9
app/client/src/App.test.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
26
app/client/src/App.tsx
Normal file
26
app/client/src/App.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
14
app/client/src/index.css
Normal file
14
app/client/src/index.css
Normal file
@ -0,0 +1,14 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
17
app/client/src/index.tsx
Normal file
17
app/client/src/index.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
7
app/client/src/logo.svg
Normal file
7
app/client/src/logo.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
app/client/src/react-app-env.d.ts
vendored
Normal file
1
app/client/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
149
app/client/src/serviceWorker.ts
Normal file
149
app/client/src/serviceWorker.ts
Normal file
@ -0,0 +1,149 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
type Config = {
|
||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
||||
};
|
||||
|
||||
export function register(config?: Config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(
|
||||
process.env.PUBLIC_URL,
|
||||
window.location.href
|
||||
);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl: string, config?: Config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
})
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then(registration => {
|
||||
registration.unregister();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error.message);
|
||||
});
|
||||
}
|
||||
}
|
5
app/client/src/setupTests.ts
Normal file
5
app/client/src/setupTests.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
25
app/client/tsconfig.json
Normal file
25
app/client/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
10951
app/client/yarn.lock
Normal file
10951
app/client/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable promise/always-return */
|
||||
/* eslint-disable promise/catch-or-return */
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* eslint-disable react/jsx-one-expression-per-line */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
@ -9,6 +14,7 @@ import { Button } from '@blueprintjs/core';
|
||||
import routes from '../constants/routes.json';
|
||||
import styles from './Home.css';
|
||||
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const [signalingServerPort, setSignalingServerPort] = useState('0000');
|
||||
const { t } = useTranslation();
|
||||
@ -42,8 +48,8 @@ export default function Home(): JSX.Element {
|
||||
<br />
|
||||
<h3 className="bp3-heading">
|
||||
{`${t(
|
||||
'Signaling server is running on port'
|
||||
)}: ${signalingServerPort}`}
|
||||
'Signaling server is running on port'
|
||||
)}: ${signalingServerPort}`}
|
||||
</h3>
|
||||
<br />
|
||||
<h3 className="bp3-heading">{`Locales test ${t('Language')}`}</h3>
|
||||
|
@ -3,9 +3,17 @@ import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import SyncBackend from 'i18next-node-fs-backend';
|
||||
import { join } from 'path';
|
||||
import isDev from 'electron-is-dev';
|
||||
// import isDev from 'electron-is-dev';
|
||||
import config from './app.config';
|
||||
|
||||
let isDev;
|
||||
try {
|
||||
// eslint-disable-next-line global-require
|
||||
isDev = require('electron-is-dev');
|
||||
} catch (e) {
|
||||
isDev = true;
|
||||
}
|
||||
|
||||
const appPath = remote.getGlobal('appPath');
|
||||
|
||||
const i18nextOptions = {
|
||||
|
@ -6,9 +6,17 @@
|
||||
import i18n from 'i18next';
|
||||
import i18nextBackend from 'i18next-node-fs-backend';
|
||||
import { join } from 'path';
|
||||
import isDev from 'electron-is-dev';
|
||||
// import isDev from 'electron-is-dev';
|
||||
import config from './app.config';
|
||||
|
||||
let isDev;
|
||||
try {
|
||||
// eslint-disable-next-line global-require
|
||||
isDev = require('electron-is-dev');
|
||||
} catch (e) {
|
||||
isDev = true;
|
||||
}
|
||||
|
||||
const i18nextOptions = {
|
||||
fallbackLng: config.fallbackLng,
|
||||
lng: 'en',
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint global-require: off, no-console: off */
|
||||
|
||||
/**
|
||||
@ -154,7 +155,7 @@ ipcMain.handle('get-signaling-server-port', () => {
|
||||
});
|
||||
|
||||
// ipcMain.on('get-initial-translations', (event, arg) => {
|
||||
ipcMain.on('get-initial-translations', (event, _) => {
|
||||
ipcMain.on('get-initial-translations', (event) => {
|
||||
// i18n.loadLanguages('en', (err, t) => {
|
||||
i18n.loadLanguages('en', () => {
|
||||
const initial = {
|
||||
|
@ -5,6 +5,10 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* base64id v0.1.0
|
||||
*/
|
||||
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
@ -44,6 +48,13 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* cookies
|
||||
* Copyright(c) 2014 Jed Schmidt, http://jed.is/
|
||||
* Copyright(c) 2015-2016 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* depd
|
||||
* Copyright(c) 2014 Douglas Christopher Wilson
|
||||
@ -62,6 +73,12 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* depd
|
||||
* Copyright(c) 2014-2018 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* depd
|
||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||
@ -141,6 +158,12 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* keygrip
|
||||
* Copyright(c) 2011-2014 Jed Schmidt
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* media-typer
|
||||
* Copyright(c) 2014 Douglas Christopher Wilson
|
||||
@ -216,6 +239,13 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* resolve-path
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
* Copyright(c) 2015-2018 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* send
|
||||
* Copyright(c) 2012 TJ Holowaychuk
|
||||
@ -264,3 +294,14 @@
|
||||
*/
|
||||
|
||||
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
|
||||
|
||||
/*! https://mths.be/utf8js v2.1.2 by @mathias */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Lodash <https://lodash.com/>
|
||||
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
app,
|
||||
Menu,
|
||||
|
26
app/server/inactive_rooms.ts
Normal file
26
app/server/inactive_rooms.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/* eslint-disable no-console */
|
||||
import getStore from './store';
|
||||
|
||||
export default async function pollForInactiveRooms() {
|
||||
const store = getStore();
|
||||
|
||||
console.log('Checking for inactive rooms...');
|
||||
const rooms = (await store.getAll('rooms')) || {};
|
||||
console.log(`${Object.keys(rooms).length} rooms found`);
|
||||
|
||||
Object.keys(rooms).forEach(async (roomId) => {
|
||||
const room = JSON.parse(rooms[roomId]);
|
||||
const timeSinceUpdatedInSeconds = (Date.now() - room.updatedAt) / 1000;
|
||||
const timeSinceUpdatedInDays = Math.round(
|
||||
timeSinceUpdatedInSeconds / 60 / 60 / 24
|
||||
);
|
||||
if (timeSinceUpdatedInDays > 7) {
|
||||
console.log(
|
||||
`Deleting roomId ${roomId} which hasn't been used in ${timeSinceUpdatedInDays} days`
|
||||
);
|
||||
await store.del('rooms', roomId);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(pollForInactiveRooms, 1000 * 60 * 60 * 12); // every 12 hours
|
||||
}
|
71
app/server/index.ts
Normal file
71
app/server/index.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/* eslint-disable no-console */
|
||||
import { Server } from 'http';
|
||||
// import express, { Request, Response, NextFunction } from 'express';
|
||||
import express from 'express';
|
||||
import getPort from 'get-port';
|
||||
// import * as bodyParser from "body-parser";
|
||||
// import express from "express";
|
||||
// import { Routes } from "./routes";
|
||||
|
||||
class SignalingServer {
|
||||
private static instance: SignalingServer;
|
||||
|
||||
public expressApp: express.Application;
|
||||
|
||||
public count: number;
|
||||
|
||||
public server: Server;
|
||||
|
||||
public port: number;
|
||||
// public routePrv: Routes = new Routes();
|
||||
|
||||
constructor() {
|
||||
this.expressApp = express();
|
||||
this.count = 0;
|
||||
this.config();
|
||||
this.server = new Server();
|
||||
this.port = 3131;
|
||||
// this.routePrv.routes(this.app);
|
||||
}
|
||||
|
||||
public async start(): Promise<Server> {
|
||||
// this.port = await getRandomPort();
|
||||
this.port = await getPort({ port: 3131 });
|
||||
this.server = this.expressApp.listen(this.port);
|
||||
console.log(`\n\nDeskreen signaling server started at port: ${this.port}`);
|
||||
return this.server;
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
private config(): void {
|
||||
// support application/json type post data
|
||||
// this.app.use(bodyParser.json());
|
||||
// support application/x-www-form-urlencoded post data
|
||||
// this.app.use(bodyParser.urlencoded({ extended: false }));
|
||||
// responde with indented JSON string
|
||||
// this.app.set("json spaces", 2);
|
||||
this.expressApp.get('/', (_, res) => {
|
||||
this.count += 1;
|
||||
res.status(200);
|
||||
res.write('Deskreen signaling server is running');
|
||||
res.send();
|
||||
});
|
||||
this.expressApp.get('/favicon.ico', (_, res) => {
|
||||
res.status(204);
|
||||
res.send();
|
||||
});
|
||||
}
|
||||
|
||||
public static getInstance(): SignalingServer {
|
||||
if (!SignalingServer.instance) {
|
||||
SignalingServer.instance = new SignalingServer();
|
||||
}
|
||||
|
||||
return SignalingServer.instance;
|
||||
}
|
||||
}
|
||||
|
||||
export default SignalingServer.getInstance();
|
@ -1,10 +1,152 @@
|
||||
import { Server } from 'http';
|
||||
// import express, { Request, Response, NextFunction } from 'express';
|
||||
/* eslint-disable no-console */
|
||||
import http, { Server } from 'http';
|
||||
import express from 'express';
|
||||
import getRandomPort from '../utils/server/getRandomPort';
|
||||
// import * as bodyParser from "body-parser";
|
||||
// import express from "express";
|
||||
// import { Routes } from "./routes";
|
||||
import https from 'https';
|
||||
import Koa from 'koa';
|
||||
import Io from 'socket.io';
|
||||
import cors from 'kcors';
|
||||
import Router from 'koa-router';
|
||||
import crypto from 'crypto';
|
||||
// import koaStatic from 'koa-static';
|
||||
import koaSend from 'koa-send';
|
||||
import getPort from 'get-port';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import Socket from './socket';
|
||||
import pollForInactiveRooms from './inactive_rooms';
|
||||
import getStore from './store';
|
||||
|
||||
let isDev;
|
||||
try {
|
||||
// eslint-disable-next-line global-require
|
||||
isDev = require('electron-is-dev');
|
||||
} catch (e) {
|
||||
isDev = true;
|
||||
}
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
|
||||
const app = new Koa();
|
||||
// const PORT = process.env.PORT || 3001;
|
||||
|
||||
const router = new Router();
|
||||
|
||||
// const appName = process.env.HEROKU_APP_NAME ? process.env.HEROKU_APP_NAME : '';
|
||||
// const isReviewApp = /-pr-/.test(appName);
|
||||
// const siteURL = process.env.SITE_URL;
|
||||
|
||||
const store = getStore();
|
||||
|
||||
// if ((siteURL || env === 'development') && !isReviewApp) {
|
||||
// app.use(
|
||||
// cors({
|
||||
// origin: env === 'development' ? '*' : siteURL,
|
||||
// allowMethods: ['GET', 'HEAD', 'POST'],
|
||||
// credentials: true,
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
app.use(cors());
|
||||
app.use(router.routes());
|
||||
|
||||
const apiHost = process.env.API_HOST;
|
||||
const cspDefaultSrc = `'self'${
|
||||
apiHost ? ` https://${apiHost} wss://${apiHost}` : ''
|
||||
}`;
|
||||
|
||||
function setStaticFileHeaders(
|
||||
ctx: Koa.ParameterizedContext<Koa.DefaultState, Koa.DefaultContext>
|
||||
) {
|
||||
ctx.set({
|
||||
'strict-transport-security': 'max-age=31536000',
|
||||
'Content-Security-Policy': `default-src ${cspDefaultSrc} 'unsafe-inline'; img-src 'self' data:;`,
|
||||
'X-Frame-Options': 'deny',
|
||||
'X-XSS-Protection': '1; mode=block',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'Referrer-Policy': 'no-referrer',
|
||||
'Feature-Policy':
|
||||
"geolocation 'none'; vr 'none'; payment 'none'; microphone 'none'",
|
||||
});
|
||||
}
|
||||
|
||||
const clientDistDirectory = isDev
|
||||
? `${__dirname}/../client/build`
|
||||
: `${__dirname}/client/build`;
|
||||
|
||||
if (clientDistDirectory) {
|
||||
app.use(async (ctx) => {
|
||||
setStaticFileHeaders(ctx);
|
||||
// await koaStatic(clientDistDirectory, {
|
||||
// maxage: ctx.req.url === '/' ? 60 * 1000 : 365 * 24 * 60 * 60 * 1000, // one minute in ms for html doc, one year for css, js, etc
|
||||
// })(ctx, next);
|
||||
});
|
||||
|
||||
app.use(async (ctx) => {
|
||||
setStaticFileHeaders(ctx);
|
||||
await koaSend(ctx, 'index.html', { root: clientDistDirectory });
|
||||
});
|
||||
} else {
|
||||
app.use(async (ctx) => {
|
||||
ctx.body = { ready: true };
|
||||
});
|
||||
}
|
||||
|
||||
const protocol = (process.env.PROTOCOL || 'http') === 'http' ? http : https;
|
||||
|
||||
const server = protocol.createServer(app.callback());
|
||||
const io = Io(server, {
|
||||
pingInterval: 20000,
|
||||
pingTimeout: 5000,
|
||||
serveClient: false,
|
||||
});
|
||||
|
||||
// Only use socket adapter if store has one
|
||||
// if (store.hasSocketAdapter) {
|
||||
// io.adapter(store.getSocketAdapter());
|
||||
// }
|
||||
|
||||
const roomHashSecret = process.env.ROOM_HASH_SECRET;
|
||||
|
||||
const getRoomIdHash = (id: string) => {
|
||||
if (env === 'development') {
|
||||
return id;
|
||||
}
|
||||
|
||||
if (roomHashSecret) {
|
||||
return crypto.createHmac('sha256', roomHashSecret).update(id).digest('hex');
|
||||
}
|
||||
|
||||
return crypto.createHash('sha256').update(id).digest('hex');
|
||||
};
|
||||
|
||||
export const getIO = () => io;
|
||||
|
||||
io.on('connection', async (socket) => {
|
||||
const { roomId } = socket.handshake.query;
|
||||
|
||||
const roomIdHash = getRoomIdHash(roomId);
|
||||
|
||||
let room = await store.get('rooms', roomIdHash);
|
||||
room = JSON.parse(room || '{}');
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Socket({
|
||||
roomIdOriginal: roomId,
|
||||
roomId: roomIdHash,
|
||||
socket,
|
||||
room,
|
||||
});
|
||||
});
|
||||
|
||||
const init = async (PORT: number) => {
|
||||
pollForInactiveRooms();
|
||||
|
||||
return server.listen(PORT, () => {
|
||||
console.log(`Signaling server is online at port ${PORT}`);
|
||||
});
|
||||
};
|
||||
|
||||
class SignalingServer {
|
||||
private static instance: SignalingServer;
|
||||
@ -28,9 +170,10 @@ class SignalingServer {
|
||||
}
|
||||
|
||||
public async start(): Promise<Server> {
|
||||
this.port = await getRandomPort();
|
||||
this.server = this.expressApp.listen(this.port);
|
||||
console.log(`\n\nSignaling server started at port: ${this.port}`);
|
||||
// this.port = await getRandomPort();
|
||||
this.port = await getPort({ port: 3131 });
|
||||
this.server = await init(this.port);
|
||||
console.log(`\n\nDeskreen signaling server started at port: ${this.port}`);
|
||||
return this.server;
|
||||
}
|
||||
|
||||
@ -46,10 +189,9 @@ class SignalingServer {
|
||||
// responde with indented JSON string
|
||||
// this.app.set("json spaces", 2);
|
||||
this.expressApp.get('/', (_, res) => {
|
||||
console.log(this.count);
|
||||
this.count += 1;
|
||||
res.status(200);
|
||||
res.write('Hello World');
|
||||
res.write('Deskreen signaling server is running');
|
||||
res.send();
|
||||
});
|
||||
this.expressApp.get('/favicon.ico', (_, res) => {
|
||||
|
199
app/server/socket.ts
Normal file
199
app/server/socket.ts
Normal file
@ -0,0 +1,199 @@
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import _ from 'lodash';
|
||||
import Io from 'socket.io';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { getIO } from './signalingServer';
|
||||
import getStore from './store';
|
||||
|
||||
interface User {
|
||||
socketId: string;
|
||||
publicKey: string;
|
||||
isOwner: boolean;
|
||||
}
|
||||
|
||||
interface Room {
|
||||
id: string;
|
||||
users: User[];
|
||||
isLocked: boolean;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
interface SocketOPTS {
|
||||
roomId: string;
|
||||
socket: Io.Socket;
|
||||
room: Room;
|
||||
roomIdOriginal: string;
|
||||
}
|
||||
|
||||
export default class Socket implements SocketOPTS {
|
||||
roomId: string;
|
||||
|
||||
socket: Io.Socket;
|
||||
|
||||
room: Room;
|
||||
|
||||
roomIdOriginal: string;
|
||||
|
||||
constructor(opts: SocketOPTS) {
|
||||
const { roomId, socket, room, roomIdOriginal } = opts;
|
||||
|
||||
this.roomId = roomId;
|
||||
this.socket = socket;
|
||||
this.roomIdOriginal = roomIdOriginal;
|
||||
this.room = room;
|
||||
if (room.isLocked) {
|
||||
this.sendRoomLocked();
|
||||
return;
|
||||
}
|
||||
|
||||
this.init(opts);
|
||||
}
|
||||
|
||||
async init(opts: SocketOPTS) {
|
||||
const { roomId, socket } = opts;
|
||||
await this.joinRoom(roomId, socket);
|
||||
this.handleSocket(socket);
|
||||
}
|
||||
|
||||
sendRoomLocked() {
|
||||
this.socket.emit('ROOM_LOCKED');
|
||||
}
|
||||
|
||||
async saveRoom(room: Room) {
|
||||
const json = {
|
||||
...room,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
return getStore().set('rooms', this.roomId, JSON.stringify(json));
|
||||
}
|
||||
|
||||
async destroyRoom() {
|
||||
return getStore().del('rooms', this.roomId);
|
||||
}
|
||||
|
||||
fetchRoom() {
|
||||
return new Promise(async (resolve) => {
|
||||
const res = await getStore().get('rooms', this.roomId);
|
||||
resolve(JSON.parse(res || '{}'));
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
joinRoom(roomId: string, socket: Io.Socket) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (getStore().hasSocketAdapter) {
|
||||
// TODO: what is this?
|
||||
// getIO()
|
||||
// .of('/')
|
||||
// .adapter.remoteJoin(socket.id, roomId, (err: Error) => {
|
||||
// if (err) {
|
||||
// reject();
|
||||
// }
|
||||
// resolve();
|
||||
// });
|
||||
} else {
|
||||
socket.join(roomId, (err) => {
|
||||
if (err) {
|
||||
reject();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handleSocket(socket: Io.Socket) {
|
||||
socket.on('ENCRYPTED_MESSAGE', (payload) => {
|
||||
socket.to(this.roomId).emit('ENCRYPTED_MESSAGE', payload);
|
||||
});
|
||||
|
||||
socket.on('USER_ENTER', async (payload) => {
|
||||
let room: Room = (await this.fetchRoom()) as Room;
|
||||
if (_.isEmpty(room)) {
|
||||
room = {
|
||||
id: this.roomId,
|
||||
users: [],
|
||||
isLocked: false,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
const newRoom: Room = {
|
||||
...room,
|
||||
users: [
|
||||
...(room.users || []),
|
||||
{
|
||||
socketId: socket.id,
|
||||
publicKey: payload.publicKey,
|
||||
isOwner: (room.users || []).length === 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
await this.saveRoom(newRoom);
|
||||
|
||||
getIO()
|
||||
.to(this.roomId)
|
||||
.emit('USER_ENTER', {
|
||||
...newRoom,
|
||||
id: this.roomIdOriginal,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('TOGGLE_LOCK_ROOM', async (__, callback) => {
|
||||
const room: Room = (await this.fetchRoom()) as Room;
|
||||
const user = (room.users || []).find(
|
||||
(u) => u.socketId === socket.id && u.isOwner
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
callback({
|
||||
isLocked: room.isLocked,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.saveRoom({
|
||||
...room,
|
||||
isLocked: !room.isLocked,
|
||||
});
|
||||
|
||||
socket.to(this.roomId).emit('TOGGLE_LOCK_ROOM', {
|
||||
locked: !room.isLocked,
|
||||
publicKey: user && user.publicKey,
|
||||
});
|
||||
|
||||
callback({
|
||||
isLocked: !room.isLocked,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => this.handleDisconnect(socket));
|
||||
|
||||
socket.on('USER_DISCONNECT', () => this.handleDisconnect(socket));
|
||||
}
|
||||
|
||||
async handleDisconnect(socket: Io.Socket) {
|
||||
const room: Room = (await this.fetchRoom()) as Room;
|
||||
|
||||
const newRoom = {
|
||||
...room,
|
||||
users: (room.users || [])
|
||||
.filter((u) => u.socketId !== socket.id)
|
||||
.map((u, index) => ({
|
||||
...u,
|
||||
isOwner: index === 0,
|
||||
})),
|
||||
};
|
||||
|
||||
await this.saveRoom(newRoom);
|
||||
|
||||
getIO().to(this.roomId).emit('USER_EXIT', newRoom.users);
|
||||
|
||||
if (newRoom.users && newRoom.users.length === 0) {
|
||||
await this.destroyRoom();
|
||||
}
|
||||
|
||||
socket.disconnect(true);
|
||||
}
|
||||
}
|
58
app/server/store/Memory.ts
Normal file
58
app/server/store/Memory.ts
Normal file
@ -0,0 +1,58 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/**
|
||||
* Memory store more for testing purpose than production use.
|
||||
*/
|
||||
|
||||
interface MemoryStoreParams {
|
||||
store: any;
|
||||
hasSocketAdapter: boolean;
|
||||
}
|
||||
|
||||
class MemoryStore implements MemoryStoreParams {
|
||||
store: any;
|
||||
|
||||
hasSocketAdapter: boolean;
|
||||
|
||||
constructor() {
|
||||
this.store = {};
|
||||
this.hasSocketAdapter = false;
|
||||
}
|
||||
|
||||
async get(key: string, field: any) {
|
||||
if (this.store[key] === undefined || this.store[key][field] === undefined) {
|
||||
return null;
|
||||
}
|
||||
return this.store[key][field];
|
||||
}
|
||||
|
||||
async getAll(key: string) {
|
||||
if (this.store[key] === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.store[key];
|
||||
}
|
||||
|
||||
async set(key: string, field: string, value: any) {
|
||||
if (this.store[key] === undefined) {
|
||||
this.store[key] = {};
|
||||
}
|
||||
this.store[key][field] = value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
async del(key: string, field: string) {
|
||||
if (this.store[key] === undefined || this.store[key][field] === undefined) {
|
||||
return 0;
|
||||
}
|
||||
delete this.store[key][field];
|
||||
return 1;
|
||||
}
|
||||
|
||||
async inc(key: string, field: string, inc = 1) {
|
||||
this.store[key][field] += inc;
|
||||
return this.store[key][field];
|
||||
}
|
||||
}
|
||||
|
||||
export default MemoryStore;
|
12
app/server/store/index.ts
Normal file
12
app/server/store/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import MemoryStore from './Memory';
|
||||
|
||||
let store: MemoryStore;
|
||||
|
||||
const getStore = () => {
|
||||
if (store === undefined) {
|
||||
store = new MemoryStore();
|
||||
}
|
||||
return store;
|
||||
};
|
||||
|
||||
export default getStore;
|
4
app/server/utils/index.ts
Normal file
4
app/server/utils/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function sanitize(str: string) {
|
||||
return str.replace(/[^A-Za-z0-9]/g, '-');
|
||||
}
|
7
app/server/utils/utils.test.ts
Normal file
7
app/server/utils/utils.test.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { sanitize } from './index';
|
||||
|
||||
test('sanitizes should strip bad characters', () => {
|
||||
expect(sanitize('d@rkW1r# e is L3git&&!&*A*')).toBe(
|
||||
'd-rkW1r--e-is-L3git-----A-'
|
||||
);
|
||||
});
|
132
app/utils/crypto.ts
Normal file
132
app/utils/crypto.ts
Normal file
@ -0,0 +1,132 @@
|
||||
/* eslint-disable no-plusplus */
|
||||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
import forge from 'node-forge';
|
||||
|
||||
export default class Crypto {
|
||||
convertStringToArrayBufferView(str: string) {
|
||||
const bytes = new Uint8Array(str.length);
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
bytes[i] = str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// convertArrayBufferViewToString(buffer) {
|
||||
// let str = '';
|
||||
// for (let i = 0; i < buffer.byteLength; i++) {
|
||||
// str += String.fromCharCode(buffer[i]);
|
||||
// }
|
||||
|
||||
// return str;
|
||||
// }
|
||||
|
||||
createEncryptDecryptKeys() {
|
||||
return new Promise<forge.pki.rsa.KeyPair>((resolve) => {
|
||||
const keypair = forge.pki.rsa.generateKeyPair({
|
||||
bits: 2048,
|
||||
e: 0x10001,
|
||||
});
|
||||
resolve(keypair);
|
||||
});
|
||||
}
|
||||
|
||||
encryptMessage(data: string, secretKey: string, iv: string) {
|
||||
return new Promise<string>((resolve) => {
|
||||
const input = forge.util.createBuffer(data, 'utf8');
|
||||
const cipherAES = forge.cipher.createCipher('AES-CBC', secretKey);
|
||||
cipherAES.start({ iv });
|
||||
cipherAES.update(input);
|
||||
cipherAES.finish();
|
||||
const cyphertext = cipherAES.output.getBytes();
|
||||
resolve(cyphertext);
|
||||
});
|
||||
}
|
||||
|
||||
decryptMessage(data: string, secretKey: string, iv: string) {
|
||||
return new Promise<string>((resolve) => {
|
||||
const input = forge.util.createBuffer(data);
|
||||
const decipher = forge.cipher.createDecipher('AES-CBC', secretKey);
|
||||
decipher.start({ iv });
|
||||
decipher.update(input); // input should be a strng here
|
||||
decipher.finish();
|
||||
const decryptedPayload = decipher.output.toString();
|
||||
resolve(decryptedPayload);
|
||||
});
|
||||
}
|
||||
|
||||
importEncryptDecryptKey(keyPemString: string) {
|
||||
return new Promise<forge.pki.rsa.PrivateKey | forge.pki.rsa.PublicKey>(
|
||||
(resolve) => {
|
||||
if (this.isPublicKeyString(keyPemString)) {
|
||||
const publicKeyPem = forge.pki.publicKeyFromPem(keyPemString);
|
||||
resolve(publicKeyPem);
|
||||
} else {
|
||||
const privateKeyPem = forge.pki.privateKeyFromPem(keyPemString);
|
||||
resolve(privateKeyPem);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
exportKey(key: forge.pki.rsa.PrivateKey | forge.pki.rsa.PublicKey) {
|
||||
return new Promise<string>((resolve) => {
|
||||
if (this.isPublicKeyObject(key)) {
|
||||
const publicKeyPem = forge.pki
|
||||
.publicKeyToPem(key as forge.pki.rsa.PublicKey)
|
||||
.toString();
|
||||
resolve(publicKeyPem);
|
||||
} else {
|
||||
const privateKeyPem = forge.pki
|
||||
.privateKeyToPem(key as forge.pki.rsa.PrivateKey)
|
||||
.toString();
|
||||
resolve(privateKeyPem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
signMessage(data: string, keyToSignWith: string) {
|
||||
return new Promise<string>((resolve) => {
|
||||
const hmac = forge.hmac.create();
|
||||
const input = forge.util.createBuffer(data, 'utf8');
|
||||
hmac.start('sha256', keyToSignWith);
|
||||
hmac.update(input);
|
||||
const signatureString = hmac.digest().getBytes();
|
||||
resolve(signatureString);
|
||||
});
|
||||
}
|
||||
|
||||
verifyPayload(signature: string, data: string, secretKey: string) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const hmac = forge.hmac.create();
|
||||
const input = forge.util.createBuffer(data, 'utf8');
|
||||
hmac.start('sha256', secretKey);
|
||||
hmac.update(input);
|
||||
const recreatedSignature = hmac.digest().getBytes();
|
||||
const verified = recreatedSignature === signature;
|
||||
resolve(verified);
|
||||
});
|
||||
}
|
||||
|
||||
wrapKeyWithForge(
|
||||
keyToWrap: string,
|
||||
publicKeyToWrapWith: forge.pki.rsa.PublicKey
|
||||
) {
|
||||
return publicKeyToWrapWith.encrypt(keyToWrap, 'RSA-OAEP');
|
||||
}
|
||||
|
||||
unwrapKey(privateKey: forge.pki.rsa.PrivateKey, encryptedAESKey: string) {
|
||||
return privateKey.decrypt(encryptedAESKey, 'RSA-OAEP');
|
||||
}
|
||||
|
||||
private isPublicKeyString(key: string) {
|
||||
return key.includes('PUBLIC KEY');
|
||||
}
|
||||
|
||||
private isPublicKeyObject(
|
||||
key: forge.pki.rsa.PublicKey | forge.pki.rsa.PrivateKey
|
||||
) {
|
||||
return (key as forge.pki.rsa.PublicKey).encrypt !== undefined;
|
||||
}
|
||||
}
|
115
app/utils/message.ts
Normal file
115
app/utils/message.ts
Normal file
@ -0,0 +1,115 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable promise/param-names */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import forge from 'node-forge';
|
||||
import Crypto from './crypto';
|
||||
|
||||
const crypto = new Crypto();
|
||||
|
||||
export const process = (payload: any, privateKeyString: string) =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
const privateKey = (await crypto.importEncryptDecryptKey(
|
||||
privateKeyString
|
||||
)) as forge.pki.rsa.PrivateKey;
|
||||
const { signature } = payload;
|
||||
const { iv } = payload;
|
||||
const payloadBuffer = payload.payload;
|
||||
|
||||
let sessionAESKeyUnencrypted = '';
|
||||
let signingHMACKey = '';
|
||||
|
||||
await new Promise((resolvePayload) => {
|
||||
payload.keys.forEach(async (key: any) => {
|
||||
try {
|
||||
sessionAESKeyUnencrypted = await crypto.unwrapKey(
|
||||
privateKey,
|
||||
key.sessionKey
|
||||
);
|
||||
signingHMACKey = await crypto.unwrapKey(privateKey, key.signingKey);
|
||||
resolvePayload();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const verified = await crypto.verifyPayload(
|
||||
signature,
|
||||
payloadBuffer,
|
||||
signingHMACKey
|
||||
);
|
||||
|
||||
if (!verified) {
|
||||
console.error("recreated signature doesn't match with payload.signature");
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
const decryptedPayload = await crypto.decryptMessage(
|
||||
payloadBuffer,
|
||||
sessionAESKeyUnencrypted,
|
||||
iv
|
||||
);
|
||||
|
||||
const payloadJson = JSON.parse(decryptedPayload);
|
||||
resolve(payloadJson);
|
||||
});
|
||||
|
||||
export const prepare = (payload: any, user: any, partner: any) =>
|
||||
new Promise(async (resolve) => {
|
||||
const myUsername = user.username;
|
||||
const myId = user.id;
|
||||
const members = [partner];
|
||||
const jsonToSend = {
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
sender: myId,
|
||||
username: myUsername,
|
||||
text: encodeURI(payload.payload.text),
|
||||
},
|
||||
};
|
||||
const payloadBuffer = JSON.stringify(jsonToSend);
|
||||
|
||||
const secretKeyRandomAES = forge.random.getBytesSync(16);
|
||||
const iv = forge.random.getBytesSync(16);
|
||||
const encryptedPayloadString = await crypto.encryptMessage(
|
||||
payloadBuffer,
|
||||
secretKeyRandomAES,
|
||||
iv
|
||||
);
|
||||
|
||||
const secretKeyRandomHMAC = forge.random.getBytesSync(32);
|
||||
const signatureString = await crypto.signMessage(
|
||||
encryptedPayloadString,
|
||||
secretKeyRandomHMAC
|
||||
);
|
||||
|
||||
const encryptedKeys = await Promise.all(
|
||||
members.map(async (member) => {
|
||||
const memberPublicKey = (await crypto.importEncryptDecryptKey(
|
||||
member.publicKey
|
||||
)) as forge.pki.rsa.PublicKey;
|
||||
const enc = await Promise.all([
|
||||
crypto.wrapKeyWithForge(secretKeyRandomAES, memberPublicKey),
|
||||
crypto.wrapKeyWithForge(secretKeyRandomHMAC, memberPublicKey),
|
||||
]);
|
||||
|
||||
return {
|
||||
sessionKey: enc[0],
|
||||
signingKey: enc[1],
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
resolve({
|
||||
toSend: {
|
||||
payload: encryptedPayloadString,
|
||||
signature: signatureString,
|
||||
iv,
|
||||
keys: encryptedKeys,
|
||||
},
|
||||
original: jsonToSend,
|
||||
});
|
||||
});
|
18
app/utils/socket.ts
Normal file
18
app/utils/socket.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/* eslint-disable no-console */
|
||||
import socketIO from 'socket.io-client';
|
||||
import generateUrl from '../api/generator';
|
||||
|
||||
let socket: SocketIOClient.Socket;
|
||||
|
||||
export const connect = (roomId: string) => {
|
||||
socket = socketIO(generateUrl(), {
|
||||
query: {
|
||||
roomId,
|
||||
},
|
||||
forceNew: true,
|
||||
});
|
||||
console.log('socket in utils', socket);
|
||||
return socket;
|
||||
};
|
||||
|
||||
export const getSocket = () => socket;
|
34
package.json
34
package.json
@ -4,12 +4,13 @@
|
||||
"version": "2.0.53",
|
||||
"description": "TODO: write description of this app",
|
||||
"scripts": {
|
||||
"build": "concurrently \"yarn build-main\" \"yarn build-renderer\"",
|
||||
"build": "concurrently \"yarn build-main\" \"yarn build-renderer\" \"yarn build-client\"",
|
||||
"build-dll": "cross-env NODE_ENV=development webpack --config ./configs/webpack.config.renderer.dev.dll.babel.js --colors",
|
||||
"build-e2e": "cross-env E2E_BUILD=true yarn build",
|
||||
"build-main": "cross-env NODE_ENV=production webpack --config ./configs/webpack.config.main.prod.babel.js --colors",
|
||||
"build-renderer": "cross-env NODE_ENV=production webpack --config ./configs/webpack.config.renderer.prod.babel.js --colors",
|
||||
"dev": "cross-env START_HOT=1 node -r @babel/register ./internals/scripts/CheckPortInUse.js && cross-env START_HOT=1 yarn start-renderer-dev",
|
||||
"build-client": "cd app/client && cross-env SKIP_PREFLIGHT_CHECK=true yarn install && cross-env SKIP_PREFLIGHT_CHECK=true yarn build",
|
||||
"dev": "cross-env START_HOT=1 node -r @babel/register ./internals/scripts/CheckPortInUse.js && concurrently \"cross-env START_HOT=1 yarn start-renderer-dev\" \"yarn start-client-dev\"",
|
||||
"coverage": "cross-env BABEL_DISABLE_CACHE=1 jest --coverage",
|
||||
"electron-rebuild": "electron-rebuild --parallel --force --types prod,dev,optional --module-dir app",
|
||||
"lint": "cross-env NODE_ENV=development eslint . --cache --ext .js,.jsx,.ts,.tsx",
|
||||
@ -31,7 +32,9 @@
|
||||
"start-main-debug": "yarn start-main-dev --inspect=5858 --remote-debugging-port=9223",
|
||||
"start-main-dev": "cross-env START_HOT=1 NODE_ENV=development electron -r ./internals/scripts/BabelRegister ./app/main.dev.ts",
|
||||
"start-renderer-dev": "cross-env NODE_ENV=development webpack-dev-server --config configs/webpack.config.renderer.dev.babel.js",
|
||||
"test": "cross-env BABEL_DISABLE_CACHE=1 jest",
|
||||
"start-client-dev": "cd app/client && cross-env SKIP_PREFLIGHT_CHECK=true yarn start",
|
||||
"test": "cross-env BABEL_DISABLE_CACHE=1 jest && yarn test-client",
|
||||
"test-client": "cd app/client && SKIP_PREFLIGHT_CHECK=true cross-env BABEL_DISABLE_CACHE=1 yarn test:nowatch",
|
||||
"test-all": "yarn lint && yarn tsc && yarn build && yarn test",
|
||||
"test-e2e": "node -r @babel/register ./internals/scripts/CheckBuildsExist.js && cross-env NODE_ENV=test testcafe electron:./app ./test/e2e/HomePage.e2e.ts",
|
||||
"test-e2e-live": "node -r @babel/register ./internals/scripts/CheckBuildsExist.js && cross-env NODE_ENV=test testcafe --live electron:./app ./test/e2e/HomePage.e2e.ts",
|
||||
@ -45,7 +48,7 @@
|
||||
"prettier --ignore-path .eslintignore --parser json --write"
|
||||
],
|
||||
"*.{css,scss}": [
|
||||
"stylelint --ignore-path .eslintignore --syntax scss --fix",
|
||||
"stylelint --allow-empty-input --ignore-path .eslintignore --syntax scss --fix",
|
||||
"prettier --ignore-path .eslintignore --single-quote --write"
|
||||
],
|
||||
"*.{html,md,yml}": [
|
||||
@ -62,7 +65,8 @@
|
||||
"main.prod.js",
|
||||
"main.prod.js.map",
|
||||
"package.json",
|
||||
"locales/"
|
||||
"locales/",
|
||||
"client/build"
|
||||
],
|
||||
"dmg": {
|
||||
"contents": [
|
||||
@ -137,6 +141,8 @@
|
||||
"homepage": "https://github.com/pavlobu/deskreen#readme",
|
||||
"jest": {
|
||||
"testURL": "http://localhost/",
|
||||
"coveragePathIgnorePatterns": ["/node_modules/", "app/client"],
|
||||
"testPathIgnorePatterns": ["/node_modules/", "app/client"],
|
||||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/internals/mocks/fileMock.js",
|
||||
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
|
||||
@ -198,7 +204,13 @@
|
||||
"@types/history": "^4.7.6",
|
||||
"@types/i18next-node-fs-backend": "^2.1.0",
|
||||
"@types/jest": "^26.0.5",
|
||||
"@types/kcors": "^2.2.3",
|
||||
"@types/koa": "^2.11.4",
|
||||
"@types/koa-router": "^7.4.1",
|
||||
"@types/koa-send": "^4.1.2",
|
||||
"@types/koa-static": "^4.0.1",
|
||||
"@types/node": "12",
|
||||
"@types/node-forge": "^0.9.5",
|
||||
"@types/react": "^16.9.44",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
@ -206,6 +218,9 @@
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/react-test-renderer": "^16.9.2",
|
||||
"@types/redux-logger": "^3.0.8",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"@types/socket.io": "^2.1.11",
|
||||
"@types/socket.io-client": "^1.4.33",
|
||||
"@types/webpack": "^4.41.21",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@typescript-eslint/eslint-plugin": "^3.6.1",
|
||||
@ -294,6 +309,12 @@
|
||||
"i18next-fs-backend": "^1.0.7",
|
||||
"i18next-node-fs-backend": "^2.1.3",
|
||||
"i18next-sync-fs-backend": "^1.1.1",
|
||||
"kcors": "^2.2.2",
|
||||
"koa": "^2.13.0",
|
||||
"koa-router": "^9.4.0",
|
||||
"koa-send": "^5.0.1",
|
||||
"koa-static": "^5.0.0",
|
||||
"node-forge": "^0.9.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-flexbox-grid": "^2.1.2",
|
||||
@ -305,6 +326,9 @@
|
||||
"redux-thunk": "^2.3.0",
|
||||
"regenerator-runtime": "^0.13.5",
|
||||
"sass": "^1.26.10",
|
||||
"shortid": "^2.2.15",
|
||||
"socket.io": "^2.3.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"source-map-support": "^0.5.19"
|
||||
},
|
||||
"devEngines": {
|
||||
|
@ -93,25 +93,25 @@ describe('Counter component', () => {
|
||||
|
||||
it('should display updated count after increment button click', () => {
|
||||
const { buttons, p } = setup();
|
||||
buttons.at(0).simulate('click');
|
||||
buttons.at(1).simulate('click');
|
||||
expect(p.text()).toMatch(/^2$/);
|
||||
});
|
||||
|
||||
it('should display updated count after decrement button click', () => {
|
||||
const { buttons, p } = setup();
|
||||
buttons.at(1).simulate('click');
|
||||
buttons.at(2).simulate('click');
|
||||
expect(p.text()).toMatch(/^0$/);
|
||||
});
|
||||
|
||||
it('shouldnt change if even and if odd button clicked', () => {
|
||||
const { buttons, p } = setup({ counter: { value: 2 } });
|
||||
buttons.at(2).simulate('click');
|
||||
buttons.at(3).simulate('click');
|
||||
expect(p.text()).toMatch(/^2$/);
|
||||
});
|
||||
|
||||
it('should change if odd and if odd button clicked', () => {
|
||||
const { buttons, p } = setup({ counter: { value: 1 } });
|
||||
buttons.at(2).simulate('click');
|
||||
buttons.at(3).simulate('click');
|
||||
expect(p.text()).toMatch(/^2$/);
|
||||
});
|
||||
});
|
||||
|
@ -1,27 +0,0 @@
|
||||
import 'regenerator-runtime/runtime';
|
||||
import signalingServer from '../../../app/server/signalingServer';
|
||||
|
||||
describe('Signaling Server', () => {
|
||||
beforeEach(async () => {
|
||||
await signalingServer.start();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
signalingServer.stop();
|
||||
});
|
||||
it('should start signaling server', async () => {
|
||||
expect(signalingServer.server.listening).toBe(true);
|
||||
});
|
||||
|
||||
it('should stop signaling server', async () => {
|
||||
signalingServer.stop();
|
||||
expect(signalingServer.server.listening).toBe(false);
|
||||
});
|
||||
|
||||
it('should have port number in range from 2000 to 9999', async () => {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const port = signalingServer.server.address().port;
|
||||
expect(port).toBeGreaterThanOrEqual(2000);
|
||||
expect(port).toBeLessThanOrEqual(9999);
|
||||
});
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
import 'regenerator-runtime/runtime';
|
||||
import signalingServer from '../../../app/server/signalingServer';
|
||||
import getRandomPort from '../../../app/utils/server/getRandomPort';
|
||||
|
||||
jest.mock('../../../app/utils/server/getRandomPort');
|
||||
|
||||
const startServerWithMockedPort = async (mockedPort) => {
|
||||
signalingServer.stop();
|
||||
getRandomPort.mockReturnValue(Promise.resolve(mockedPort));
|
||||
await signalingServer.start();
|
||||
};
|
||||
test('should have exact port number as getRandomPort provided', async () => {
|
||||
const expectedPort = 3333;
|
||||
await startServerWithMockedPort(expectedPort);
|
||||
expect(signalingServer.server.address().port).toBe(expectedPort);
|
||||
signalingServer.stop();
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import 'regenerator-runtime/runtime';
|
||||
import getRandomPort from '../../../app/utils/server/getRandomPort';
|
||||
|
||||
test('shold generate random port in range 2000 to 9999', async () => {
|
||||
const port = await getRandomPort();
|
||||
expect(port).toBeGreaterThanOrEqual(2000);
|
||||
expect(port).toBeLessThanOrEqual(9999);
|
||||
});
|
@ -29,6 +29,7 @@
|
||||
"app/main.prod.js",
|
||||
"app/renderer.prod.js",
|
||||
"app/dist",
|
||||
"dll"
|
||||
"dll",
|
||||
"app/client"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user