1
0
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:
Pavlo Buidenkov 2020-08-26 21:23:50 +03:00
parent 86a108c1ea
commit a3996c7a76
52 changed files with 13321 additions and 478 deletions

View File

@ -54,3 +54,7 @@ package.json
*.css.d.ts
*.sass.d.ts
*.scss.d.ts
# Entire app/client directory
app/client/*

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -3,6 +3,9 @@
test-reports/
test-reporter.xml
app/client/node_modules
app/client/build
# Logs
logs
*.log

View File

@ -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
View 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
View 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
View 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
View 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 cant go back!**
If you arent 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 youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt 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
View 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"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
app/client/src/App.css Normal file
View 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);
}
}

View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View 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);
});
}
}

View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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 = {

View File

@ -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',

View File

@ -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 = {

View File

@ -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
*/

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
app,
Menu,

View 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
View 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();

View File

@ -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
View 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);
}
}

View 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
View 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;

View 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, '-');
}

View 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
View 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
View 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
View 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;

View File

@ -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": {

View File

@ -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$/);
});
});

View File

@ -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);
});
});

View File

@ -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();
});

View File

@ -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);
});

View File

@ -29,6 +29,7 @@
"app/main.prod.js",
"app/renderer.prod.js",
"app/dist",
"dll"
"dll",
"app/client"
]
}

1326
yarn.lock

File diff suppressed because it is too large Load Diff