├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── .prettierrc.js
├── .travis.yml
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── example
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── README.md
├── build
│ └── index.html
├── config-overrides.js
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── App.tsx
│ ├── components
│ │ ├── AppFooter
│ │ │ └── AppFooter.tsx
│ │ ├── AppHeader
│ │ │ └── AppHeader.tsx
│ │ ├── Button
│ │ │ └── Button.ts
│ │ ├── Chat
│ │ │ ├── Chat.tsx
│ │ │ └── ChatMessage.tsx
│ │ ├── FileSharing
│ │ │ └── FileSharing.tsx
│ │ ├── Host
│ │ │ └── Host.tsx
│ │ ├── HostOrSlave
│ │ │ └── HostOrSlave.tsx
│ │ ├── PageHeader
│ │ │ └── PageHeader.ts
│ │ ├── Slave
│ │ │ └── Slave.tsx
│ │ ├── Space
│ │ │ └── Space.tsx
│ │ └── TextArea
│ │ │ └── TextArea.ts
│ ├── index.tsx
│ ├── module
│ │ ├── ChatMessages
│ │ │ └── ChatMessages.tsx
│ │ ├── FileBuffers
│ │ │ └── FileBuffers.tsx
│ │ ├── PeerConnection
│ │ │ └── PeerConnection.tsx
│ │ └── useChat
│ │ │ └── useChat.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.js
│ ├── styles
│ │ ├── globalStyle.ts
│ │ └── normalizeCss.ts
│ ├── types
│ │ ├── ChatMessageType.ts
│ │ ├── MessagePayloadType.ts
│ │ ├── MessageSenderEnum.ts
│ │ ├── MessageType.ts
│ │ └── MessageTypeEnum.ts
│ └── util
│ │ ├── arrayBufferConverter.ts
│ │ ├── connectionDescriptionEncoding.ts
│ │ ├── connectionDescriptionValidator.ts
│ │ ├── encryption.spec.ts
│ │ └── encryption.ts
└── tsconfig.json
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── createPeerConnection.ts
└── index.ts
├── test
└── test.spec.ts
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | typescript-lib
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | extends: [
4 | 'plugin:@typescript-eslint/recommended',
5 | 'prettier/@typescript-eslint',
6 | 'plugin:prettier/recommended',
7 | ],
8 | parserOptions: {
9 | ecmaVersion: 2018,
10 | sourceType: 'module',
11 | },
12 | rules: {
13 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_|^req|^next' }],
14 | '@typescript-eslint/no-explicit-any': 0,
15 | '@typescript-eslint/explicit-function-return-type': 0,
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | dist
4 | .rpt2_cache
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry = "https://registry.npmjs.com/"
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: 'all',
4 | singleQuote: true,
5 | printWidth: 120,
6 | tabWidth: 2,
7 | };
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '11'
4 | - '10'
5 | - '8'
6 | - '6'
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2,
3 | "editor.detectIndentation": false,
4 | "editor.renderWhitespace": "boundary",
5 | "files.exclude": {
6 | "**/dist": true,
7 | "**/.rpt2_cache": true,
8 | "**/node_modules": true
9 | },
10 | "eslint.autoFixOnSave": true,
11 | "eslint.workingDirectories": ["example", "src", "test"],
12 | "eslint.validate": [
13 | "javascript",
14 | "javascriptreact",
15 | { "language": "typescript", "autoFix": true },
16 | { "language": "typescriptreact", "autoFix": true }
17 | ],
18 | "workbench.editor.enablePreview": false,
19 | "workbench.editor.enablePreviewFromQuickOpen": false,
20 | "editor.codeActionsOnSave": {
21 | "source.fixAll.eslint": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 [these people](https://github.com/rollup/rollup-starter-lib/graphs/contributors)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # p2p-chat
2 | 
3 |
4 | p2p chat on WebRTC with additional AES256 encryption and file sharing (no signaling server required)
5 |
6 | # [>pitu-pitu chat<](https://michal-wrzosek.github.io/p2p-chat/)
7 | The source code of demo chat is [here](https://github.com/michal-wrzosek/p2p-chat/tree/master/example/src)
8 |
9 | This is an example of how you can build p2p chat on WebRTC with no signaling servers. It should work in both Chrome and Firefox. WebRTC needs STUN and TURN servers to successfully establish p2p connection over the network. In my demo app I used some publicly available endpoints:
10 | - stun:stun.l.google.com:19302
11 | - turn:turn.anyfirewall.com:443?transport=tcp (webrtc:webrtc)
12 |
13 | Additional features:
14 | - AES256 encryption to all messages and files
15 | - file sharing
16 | - chat available as a single HTML file with no dependencies over the network so you can just save that file locally [index.html](https://raw.githubusercontent.com/michal-wrzosek/p2p-chat/master/example/build/index.html)
17 |
18 | Since there's no signaling server in between, you have to send a WebRTC connection description manually to your friend :D Sounds funny but it works (like 70% of times - sometimes you have to try to connect one more time by reloading a chat)
19 |
20 | # Library
21 |
22 | I made a small wrapper around WebRTC for the purpose of constructing a demo chat. It boils down to a function I called `createPeerConnection`.
23 |
24 | ### To install:
25 | ```
26 | npm install --save p2p-chat
27 | ```
28 |
29 | ### To use:
30 |
31 | To initiate a new connection (as a HOST):
32 | ```typescript
33 | import { createPeerConnection } from 'p2p-chat';
34 |
35 | const iceServers: RTCIceServer[] = [
36 | {
37 | urls: 'stun:stun.l.google.com:19302',
38 | },
39 | {
40 | urls: 'turn:turn.anyfirewall.com:443?transport=tcp',
41 | username: 'webrtc',
42 | credential: 'webrtc',
43 | },
44 | ];
45 |
46 | const someAsyncFunc = async () => {
47 | const onChannelOpen = () => console.log(`Connection ready!`);
48 | const onMessageReceived = (message: string) => console.log(`New incomming message: ${message}`);
49 |
50 | const { localDescription, setAnswerDescription, sendMessage } = await createPeerConnection({ iceServers, onMessageReceived, onChannelOpen });
51 |
52 | // you will send localDescription to your SLAVE and he will give you his localDescription. You will set it as an answer to establish connection
53 | const answerDescription = 'This is a string you will get from a SLAVE trying to connect with your localDescription';
54 | setAnswerDescription(answerDescription);
55 |
56 | // later on you can send a message to SLAVE
57 | sendMessage('Hello SLAVE');
58 | }
59 | ```
60 |
61 | To join a connection (as a SLAVE):
62 | ```typescript
63 | import { createPeerConnection } from 'p2p-chat';
64 |
65 | const iceServers: RTCIceServer[] = [
66 | {
67 | urls: 'stun:stun.l.google.com:19302',
68 | },
69 | {
70 | urls: 'turn:turn.anyfirewall.com:443?transport=tcp',
71 | username: 'webrtc',
72 | credential: 'webrtc',
73 | },
74 | ];
75 |
76 | const someAsyncFunc = async () => {
77 | const remoteDescription = 'This is a string you will get from a host...';
78 | const onChannelOpen = () => console.log(`Connection ready!`);
79 | const onMessageReceived = (message: string) => console.log(`New incomming message: ${message}`);
80 |
81 | const { localDescription, sendMessage } = await createPeerConnection({ remoteDescription, iceServers, onMessageReceived, onChannelOpen });
82 |
83 | // Send your local description to HOST and wait for a connection to start
84 |
85 | // Later on you can send a message to HOST
86 | sendMessage('Hello HOST');
87 | };
88 | ```
89 |
90 | You can take a look how I implemented it in a demo chat app:
91 | [example/src/App.tsx](https://github.com/michal-wrzosek/p2p-chat/blob/master/example/src/App.tsx)
92 |
93 | ### Typescript
94 | This lib has types already built in. No need for @types/...
95 |
96 | ---
97 |
98 | This package was bootstrapped with [typescript-lib-boilerplate](https://github.com/michal-wrzosek/typescript-lib-boilerplate)
99 |
--------------------------------------------------------------------------------
/example/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | extends: [
4 | 'plugin:react/recommended',
5 | 'plugin:react-hooks/recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'prettier/@typescript-eslint',
8 | 'plugin:prettier/recommended',
9 | ],
10 | parserOptions: {
11 | ecmaVersion: 2018,
12 | sourceType: 'module',
13 | ecmaFeatures: {
14 | jsx: true,
15 | },
16 | },
17 | rules: {
18 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_|^req|^next' }],
19 | '@typescript-eslint/no-explicit-any': 0,
20 | '@typescript-eslint/explicit-function-return-type': 0,
21 | '@typescript-eslint/no-empty-interface': 0,
22 | 'react/prop-types': 0,
23 | },
24 | settings: {
25 | react: {
26 | version: 'detect',
27 | },
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | # /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | /src/typescript-lib
26 |
--------------------------------------------------------------------------------
/example/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: 'all',
4 | singleQuote: true,
5 | printWidth: 120,
6 | tabWidth: 2,
7 | };
8 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | 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.
35 |
36 | 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.
37 |
38 | 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.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/example/config-overrides.js:
--------------------------------------------------------------------------------
1 | class RemoveCssLinkPlugin {
2 | apply(compiler) {
3 | compiler.hooks.emit.tapAsync('RemoveCssLinkPlugin', (compilation, callback) => {
4 | compilation.assets = { 'index.html': compilation.assets['index.html'] };
5 |
6 | const newSource = compilation.assets['index.html']
7 | .source()
8 | .replace(//, '');
9 |
10 | compilation.assets['index.html'].source = () => newSource;
11 |
12 | callback();
13 | });
14 | }
15 | }
16 |
17 | module.exports = function override(config, webpackEnv) {
18 | const isEnvProduction = webpackEnv === 'production';
19 |
20 | if (!isEnvProduction) return config;
21 |
22 | // No chunks. We need a single JS and CSS bundle
23 | config.optimization.runtimeChunk = false;
24 | config.optimization.splitChunks = {
25 | cacheGroups: {
26 | default: false,
27 | },
28 | };
29 |
30 | // Inline all JS and CSS files into index.html file
31 | config.plugins[1].tests[0] = /.+[.](js|css)/;
32 |
33 | // Remove meta tag with link to unexisting CSS file
34 | config.plugins.push(new RemoveCssLinkPlugin());
35 |
36 | return config;
37 | };
38 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://michal-wrzosek.github.io/p2p-chat",
6 | "dependencies": {
7 | "copy-to-clipboard": "^3.3.1",
8 | "crypto-js": "^4.0.0",
9 | "js-base64": "^2.5.2",
10 | "react": "^16.13.1",
11 | "react-dom": "^16.13.1",
12 | "react-scripts": "^3.4.1",
13 | "rxjs": "^6.5.5",
14 | "schemat": "^2.0.1",
15 | "shortid": "^2.2.15",
16 | "styled-components": "^5.1.0"
17 | },
18 | "devDependencies": {
19 | "@types/crypto-js": "^3.1.44",
20 | "@types/jest": "^25.2.1",
21 | "@types/js-base64": "^2.3.1",
22 | "@types/node": "^13.11.1",
23 | "@types/react": "^16.9.34",
24 | "@types/react-dom": "^16.9.6",
25 | "@types/shortid": "0.0.29",
26 | "@types/styled-components": "^5.0.1",
27 | "eslint-plugin-react": "^7.19.0",
28 | "eslint-plugin-react-hooks": "^3.0.0",
29 | "gh-pages": "^2.2.0",
30 | "react-app-rewired": "^2.1.5",
31 | "rimraf": "^3.0.2",
32 | "typescript": "^3.8.3"
33 | },
34 | "scripts": {
35 | "predeploy": "npm run build",
36 | "deploy": "gh-pages -d build",
37 | "start": "react-app-rewired start",
38 | "build:app": "react-app-rewired build",
39 | "build:clean": "rimraf 'build/!(index.html)'",
40 | "build": "npm run build:app && npm run build:clean",
41 | "test": "react-app-rewired test",
42 | "eject": "react-scripts eject"
43 | },
44 | "eslintConfig": {
45 | "extends": "react-app"
46 | },
47 | "browserslist": [
48 | ">0.2%",
49 | "not dead",
50 | "not ie <= 11",
51 | "not op_mini all"
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 | pitu-pitu
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, FC } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { Chat } from './components/Chat/Chat';
5 | import { HostOrSlave } from './components/HostOrSlave/HostOrSlave';
6 | import { Host } from './components/Host/Host';
7 | import { Slave } from './components/Slave/Slave';
8 | import { AppHeader } from './components/AppHeader/AppHeader';
9 | import { AppFooter } from './components/AppFooter/AppFooter';
10 | import { useChat, useChatPeerConnectionSubscription } from './module/useChat/useChat';
11 | import { PEER_CONNECTION_MODE } from './module/PeerConnection/PeerConnection';
12 |
13 | const InnerWrapper = styled.div`
14 | background-color: white;
15 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
16 | `;
17 | const Wrapper = styled.div`
18 | padding: 12px;
19 | max-width: 320px;
20 | margin: 0 auto;
21 | `;
22 |
23 | const App: FC = memo(function App() {
24 | const { mode, isConnected } = useChat();
25 | useChatPeerConnectionSubscription();
26 |
27 | return (
28 |
29 |
30 |
31 | {!mode && }
32 | {mode === PEER_CONNECTION_MODE.HOST && !isConnected && }
33 | {mode === PEER_CONNECTION_MODE.SLAVE && !isConnected && }
34 | {mode && isConnected && }
35 |
36 |
37 |
38 | );
39 | });
40 |
41 | export default App;
42 |
--------------------------------------------------------------------------------
/example/src/components/AppFooter/AppFooter.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import styled from 'styled-components';
3 |
4 | const Homepage = styled.div``;
5 | const Version = styled.div``;
6 | const Line = styled.div`
7 | width: 100%;
8 | height: 1px;
9 | background-color: white;
10 | `;
11 | const InnerContainer = styled.div`
12 | width: 100%;
13 | display: flex;
14 | justify-content: space-between;
15 | align-items: center;
16 | background-color: black;
17 | color: white;
18 | font-size: 8px;
19 | padding: 5px 8px 4px;
20 | `;
21 | const Container = styled.div`
22 | display: flex;
23 | flex-direction: column;
24 | background-color: black;
25 | border: 1px solid black;
26 | border-top: none;
27 | `;
28 |
29 | interface Props {
30 | version: string;
31 | homepage: string;
32 | }
33 |
34 | export const AppFooter: FC = ({ version, homepage }) => (
35 |
36 |
37 |
38 | {version}
39 | {homepage}
40 |
41 |
42 | );
43 |
--------------------------------------------------------------------------------
/example/src/components/AppHeader/AppHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import styled from 'styled-components';
3 |
4 | const SubTitle = styled.h2`
5 | font-size: 10px;
6 | font-weight: 400;
7 | text-align: center;
8 | margin: 0;
9 | padding: 4px;
10 | `;
11 | const Title = styled.h1`
12 | font-size: 16px;
13 | font-weight: 400;
14 | background-color: black;
15 | color: white;
16 | text-align: center;
17 | margin: 0;
18 | padding: 4px;
19 |
20 | > span {
21 | display: inline-block;
22 | padding-left: 3px;
23 | transform: translate(0, 2px);
24 | }
25 | `;
26 | const Container = styled.div`
27 | display: flex;
28 | flex-direction: column;
29 | border: 1px solid black;
30 | border-bottom: none;
31 | `;
32 |
33 | export const AppHeader: FC = () => (
34 |
35 |
36 |
37 | 😃
38 |
39 | pitu-pitu
40 |
41 | 😃
42 |
43 |
44 | p2p chat on WebRTC with additional AES256 encryption and file sharing
45 |
46 | );
47 |
--------------------------------------------------------------------------------
/example/src/components/Button/Button.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Button = styled.button`
4 | display: block;
5 | font-size: 10px;
6 | color: white;
7 | width: 100%;
8 | height: 30px;
9 | padding: 0 8px;
10 | border: none;
11 | outline: none;
12 | appearance: none;
13 | background-image: none;
14 | background-color: black;
15 | box-shadow: none;
16 | cursor: pointer;
17 | `;
18 |
--------------------------------------------------------------------------------
/example/src/components/Chat/Chat.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, memo, FC, useRef, useState } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { useChat } from '../../module/useChat/useChat';
5 | import { ChatMessage } from './ChatMessage';
6 | import { PageHeader } from '../PageHeader/PageHeader';
7 | import { TextArea } from '../TextArea/TextArea';
8 | import { Button } from '../Button/Button';
9 | import { FileSharing } from '../FileSharing/FileSharing';
10 |
11 | const MessageButton = styled(Button)`
12 | width: 300%;
13 | `;
14 | const StyledFileSharing = styled(FileSharing)``;
15 | const MessageTextArea = styled(TextArea)`
16 | width: 700%;
17 | `;
18 | const MessageForm = styled.form`
19 | width: 100%;
20 | display: flex;
21 | `;
22 | const MessagesInnerContainer = styled.div`
23 | width: 100%;
24 | display: flex;
25 | flex-direction: column;
26 | `;
27 | const MessagesContainer = styled.div`
28 | width: 100%;
29 | height: 300px;
30 | overflow-y: scroll;
31 | display: flex;
32 | flex-direction: column-reverse;
33 | border: 1px solid black;
34 | border-top: none;
35 | border-bottom: none;
36 | `;
37 | const Container = styled.div`
38 | width: 100%;
39 | display: flex;
40 | flex-direction: column;
41 | `;
42 |
43 | export const Chat: FC = memo(function Chat() {
44 | const { chatMessages, sendTextChatMessage } = useChat();
45 | const [messageToSend, setMessageToSend] = useState('');
46 | const formRef = useRef();
47 |
48 | const send = useCallback(() => {
49 | sendTextChatMessage(messageToSend);
50 | setMessageToSend('');
51 | }, [sendTextChatMessage, messageToSend, setMessageToSend]);
52 |
53 | const handleTextAreaChange: React.ChangeEventHandler = useCallback(
54 | (event) => {
55 | event.preventDefault();
56 | event.stopPropagation();
57 | setMessageToSend(event.target.value);
58 | },
59 | [setMessageToSend],
60 | );
61 |
62 | const handleTextAreaKeyDown: React.KeyboardEventHandler = useCallback(
63 | (event) => {
64 | if ((event.which !== 13 && event.keyCode !== 13) || event.shiftKey) return;
65 | if (!formRef.current) return;
66 |
67 | event.preventDefault();
68 | event.stopPropagation();
69 | send();
70 | },
71 | [send],
72 | );
73 |
74 | const handleSubmit: React.FormEventHandler = useCallback(
75 | (event) => {
76 | event.preventDefault();
77 | event.stopPropagation();
78 | send();
79 | },
80 | [send],
81 | );
82 |
83 | return (
84 |
85 | Chat
86 |
87 |
88 | {chatMessages.map((chatMessage) => (
89 |
90 | ))}
91 |
92 |
93 | } onSubmit={handleSubmit}>
94 |
95 |
101 | Send
102 |
103 |
104 | );
105 | });
106 |
--------------------------------------------------------------------------------
/example/src/components/Chat/ChatMessage.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo } from 'react';
2 | import styled, { css } from 'styled-components';
3 |
4 | import { MESSAGE_SENDER } from '../../types/MessageSenderEnum';
5 | import { ChatMessageType } from '../../types/ChatMessageType';
6 | import { useFileBuffer } from '../../module/FileBuffers/FileBuffers';
7 |
8 | const Text = styled.div``;
9 | const Header = styled.div`
10 | font-size: 12px;
11 |
12 | > span {
13 | font-weight: 700;
14 | }
15 | `;
16 | const Message = styled.div<{ sender: MESSAGE_SENDER }>`
17 | padding: 4px;
18 | background-color: greenyellow;
19 |
20 | ${({ sender }) =>
21 | sender === MESSAGE_SENDER.ME
22 | ? css`
23 | background-color: cyan;
24 | `
25 | : ''};
26 | `;
27 |
28 | interface ChatFileMessageType extends ChatMessageType {
29 | fileId: string;
30 | }
31 |
32 | interface ChatFileMessageProps {
33 | chatMessage: ChatFileMessageType;
34 | }
35 |
36 | const ChatFileMessage: FC = memo(function ChatFileMessage({ chatMessage }) {
37 | const { fileName, fileSize, receivedSize, receivedBlobUrl } = useFileBuffer(chatMessage.fileId);
38 |
39 | if (typeof fileSize === 'undefined' || typeof fileName === 'undefined') {
40 | return (
41 |
42 |
43 | {chatMessage.sender === MESSAGE_SENDER.ME ? 'Me' : 'Friend'} (
44 | {new Date(chatMessage.timestamp).toLocaleTimeString()})
45 |
46 | File in progress...
47 |
48 | );
49 | }
50 |
51 | if (!receivedBlobUrl) {
52 | return (
53 |
54 |
55 | {chatMessage.sender === MESSAGE_SENDER.ME ? 'Me' : 'Friend'} (
56 | {new Date(chatMessage.timestamp).toLocaleTimeString()})
57 |
58 |
59 | {fileName} {Math.floor((receivedSize / fileSize) * 100)}%
60 |
61 |
62 | );
63 | }
64 |
65 | return (
66 |
67 |
68 | {chatMessage.sender === MESSAGE_SENDER.ME ? 'Me' : 'Friend'} (
69 | {new Date(chatMessage.timestamp).toLocaleTimeString()})
70 |
71 |
72 |
73 | {fileName}
74 |
75 |
76 |
77 | );
78 | });
79 |
80 | const ChatTextMessage: FC = memo(function ChatTextMessage({ chatMessage }) {
81 | return (
82 |
83 |
84 | {chatMessage.sender === MESSAGE_SENDER.ME ? 'Me' : 'Friend'} (
85 | {new Date(chatMessage.timestamp).toLocaleTimeString()})
86 |
87 | {chatMessage.text}
88 |
89 | );
90 | });
91 |
92 | interface Props {
93 | chatMessage: ChatMessageType;
94 | }
95 |
96 | export const ChatMessage: FC = memo(function ChatMessage({ chatMessage }) {
97 | if (chatMessage.fileId) return ;
98 |
99 | return ;
100 | });
101 |
--------------------------------------------------------------------------------
/example/src/components/FileSharing/FileSharing.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useRef, FC, ChangeEventHandler, MouseEventHandler, memo, useCallback } from 'react';
2 | import styled from 'styled-components';
3 | import shortid from 'shortid';
4 |
5 | import { Button } from '../Button/Button';
6 | import { useChat } from '../../module/useChat/useChat';
7 | import { arrayBufferToString } from '../../util/arrayBufferConverter';
8 |
9 | export interface FileSharingProps {
10 | className?: string;
11 | }
12 |
13 | const Input = styled.input`
14 | display: none;
15 | `;
16 | const StyledButton = styled(Button)``;
17 |
18 | export const FileSharing: FC = memo(function FileSharing({ className }) {
19 | const { sendFileInfo, sendFileChunk } = useChat();
20 | const inputRef = useRef() as React.MutableRefObject;
21 |
22 | const handleButtonClick: MouseEventHandler = useCallback(
23 | (event) => {
24 | event.preventDefault();
25 | event.stopPropagation();
26 | if (!inputRef.current) return;
27 | inputRef.current.click();
28 | },
29 | [inputRef],
30 | );
31 |
32 | const handleInputChange: ChangeEventHandler = useCallback(
33 | (event) => {
34 | const file = event.target.files?.[0];
35 |
36 | if (!file) return;
37 |
38 | const fileId = shortid.generate();
39 | const BYTES_PER_CHUNK = 1200;
40 | const fileReader = new FileReader();
41 | let currentChunk = 0;
42 |
43 | const readNextChunk = () => {
44 | const start = BYTES_PER_CHUNK * currentChunk;
45 | const end = Math.min(file.size, start + BYTES_PER_CHUNK);
46 | fileReader.readAsArrayBuffer(file.slice(start, end));
47 | };
48 |
49 | fileReader.onload = () => {
50 | if (!(fileReader.result instanceof ArrayBuffer)) return;
51 |
52 | sendFileChunk({ fileId, fileChunkIndex: currentChunk, fileChunk: arrayBufferToString(fileReader.result) });
53 | currentChunk++;
54 |
55 | if (BYTES_PER_CHUNK * currentChunk < file.size) {
56 | readNextChunk();
57 | }
58 | };
59 |
60 | sendFileInfo({
61 | fileId,
62 | fileName: file.name,
63 | fileSize: file.size,
64 | });
65 |
66 | readNextChunk();
67 | },
68 | [sendFileInfo, sendFileChunk],
69 | );
70 |
71 | return (
72 |
73 |
74 |
75 |
76 | 📎
77 |
78 |
79 |
80 | );
81 | });
82 |
--------------------------------------------------------------------------------
/example/src/components/Host/Host.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, createRef, FC, memo } from 'react';
2 | import styled from 'styled-components';
3 | import copy from 'copy-to-clipboard';
4 |
5 | import { encode, decode } from '../../util/connectionDescriptionEncoding';
6 | import { connectionDescriptionValidator } from '../../util/connectionDescriptionValidator';
7 | import { PageHeader } from '../PageHeader/PageHeader';
8 | import { TextArea } from '../TextArea/TextArea';
9 | import { Button } from '../Button/Button';
10 | import { useChat } from '../../module/useChat/useChat';
11 | import { ConnectionDescription } from '../../module/PeerConnection/PeerConnection';
12 |
13 | const ErrorMessage = styled.div``;
14 | const StyledTextArea = styled(TextArea)`
15 | width: 70%;
16 | `;
17 | const ConnectButton = styled(Button)`
18 | width: 70%;
19 | margin-top: 4px;
20 | `;
21 | const CopyButton = styled(Button)`
22 | width: 70%;
23 | margin-top: 4px;
24 | `;
25 | const Form = styled.form`
26 | width: 100%;
27 | display: flex;
28 | flex-direction: column;
29 | align-items: center;
30 | `;
31 | const Instruction = styled.div`
32 | font-size: 10px;
33 | color: black;
34 | margin-bottom: 4px;
35 | `;
36 | const Step = styled.div`
37 | position: absolute;
38 | top: 8px;
39 | left: 8px;
40 | width: 18px;
41 | height: 18px;
42 | background-color: black;
43 | color: white;
44 | font-size: 10px;
45 | border-radius: 50%;
46 | line-height: 1;
47 | display: flex;
48 | justify-content: center;
49 | align-items: center;
50 | text-align: center;
51 |
52 | > span {
53 | display: inline-block;
54 | transform: translate(0.5px, -0.5px);
55 | }
56 | `;
57 | const Card = styled.div`
58 | position: relative;
59 | width: 100%;
60 | display: flex;
61 | flex-direction: column;
62 | align-items: center;
63 | padding: 24px;
64 | border: 1px solid black;
65 | border-top: none;
66 | `;
67 | const Container = styled.div`
68 | width: 100%;
69 | display: flex;
70 | flex-direction: column;
71 | `;
72 |
73 | export interface HostProps {
74 | connectionDescription: ConnectionDescription;
75 | onSubmit: (remoteConnectionDescription: ConnectionDescription) => any;
76 | }
77 |
78 | export const Host: FC = memo(function Host() {
79 | const { localConnectionDescription, setRemoteConnectionDescription } = useChat();
80 | const [remoteConnectionDescriptionInputValue, setRemoteConnectionDescriptionInputValue] = useState('');
81 | const [error, setError] = useState('');
82 | const copyTextAreaRef = createRef();
83 |
84 | const encodedConnectionDescription = encode(localConnectionDescription as ConnectionDescription);
85 |
86 | const handleCopyClick = () => {
87 | if (!copyTextAreaRef.current) return;
88 |
89 | copyTextAreaRef.current.select();
90 | copy(encodedConnectionDescription);
91 | };
92 |
93 | const handleRemoteConnectionDescriptionInputChange: React.ChangeEventHandler = (event) => {
94 | event.preventDefault();
95 | event.stopPropagation();
96 | setError('');
97 | setRemoteConnectionDescriptionInputValue(event.target.value);
98 | };
99 |
100 | const handleSubmit: React.FormEventHandler = (event) => {
101 | try {
102 | event.stopPropagation();
103 | event.preventDefault();
104 | const connectionDescriptionObject = decode(remoteConnectionDescriptionInputValue);
105 | if (connectionDescriptionValidator(connectionDescriptionObject)) throw new Error();
106 | setRemoteConnectionDescription(connectionDescriptionObject as ConnectionDescription);
107 | } catch (error) {
108 | setError('Connection Description invalid!');
109 | }
110 | };
111 |
112 | return (
113 |
114 | Starting a new chat
115 |
116 |
117 | 1
118 |
119 | Send this code to your buddy:
120 |
121 | Copy to clipboard
122 |
123 |
124 |
125 | 2
126 |
127 |
136 | {!!error && {error}}
137 |
138 |
139 | );
140 | });
141 |
--------------------------------------------------------------------------------
/example/src/components/HostOrSlave/HostOrSlave.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useState, MouseEventHandler, ChangeEventHandler, FormEventHandler, memo } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { connectionDescriptionValidator } from '../../util/connectionDescriptionValidator';
5 | import { decode } from '../../util/connectionDescriptionEncoding';
6 | import { PageHeader } from '../PageHeader/PageHeader';
7 | import { Button } from '../Button/Button';
8 | import { TextArea } from '../TextArea/TextArea';
9 | import { useChat } from '../../module/useChat/useChat';
10 | import { ConnectionDescription } from '../../module/PeerConnection/PeerConnection';
11 |
12 | const InvitationTextArea = styled(TextArea)`
13 | width: 100px;
14 | `;
15 | const HostButton = styled(Button)`
16 | width: 100px;
17 | `;
18 | const SlaveButton = styled(Button)`
19 | width: 100px;
20 | margin-top: 4px;
21 | `;
22 | const ErrorMessage = styled.div``;
23 | const Form = styled.form`
24 | display: flex;
25 | flex-direction: column;
26 | justify-content: center;
27 | align-items: center;
28 | `;
29 | const Card = styled.div`
30 | display: flex;
31 | flex-direction: column;
32 | justify-content: center;
33 | align-items: center;
34 | `;
35 | const Or = styled.div`
36 | position: absolute;
37 | top: 50%;
38 | left: 50%;
39 | display: flex;
40 | justify-content: center;
41 | align-items: center;
42 | text-align: center;
43 | width: 20px;
44 | height: 20px;
45 | font-size: 10px;
46 | background-color: black;
47 | color: white;
48 | border-radius: 50%;
49 | transform: translate(-50%, -50%);
50 |
51 | > span {
52 | transform: translate(0.5px, -1.5px);
53 | line-height: 1;
54 | }
55 | `;
56 | const CardContainer = styled.div`
57 | position: relative;
58 | display: flex;
59 | width: 100%;
60 | height: 200px;
61 | align-items: stretch;
62 | border: 1px solid black;
63 | border-top: none;
64 |
65 | > ${Card} {
66 | width: 100%;
67 | border-left: 1px solid black;
68 |
69 | &:first-child {
70 | border-left: none;
71 | }
72 | }
73 | `;
74 | const Container = styled.div`
75 | display: flex;
76 | flex-direction: column;
77 | `;
78 |
79 | export const HostOrSlave: FC = memo(function HostOrSlave() {
80 | const { startAsHost, startAsSlave } = useChat();
81 | const [connectionDescription, setConnectionDescription] = React.useState('');
82 | const [error, setError] = useState('');
83 |
84 | const handleHostBtnClick: MouseEventHandler = (event) => {
85 | event.preventDefault();
86 | event.stopPropagation();
87 | startAsHost();
88 | };
89 |
90 | const handleConnectionDescriptionInputChange: ChangeEventHandler = (event) => {
91 | event.preventDefault();
92 | event.stopPropagation();
93 | setError('');
94 | setConnectionDescription(event.target.value);
95 | };
96 |
97 | const handleSlaveFormSubmit: FormEventHandler = (event) => {
98 | event.preventDefault();
99 | event.stopPropagation();
100 |
101 | try {
102 | const connectionDescriptionObject = decode(connectionDescription);
103 | if (connectionDescriptionValidator(connectionDescriptionObject)) throw new Error();
104 | startAsSlave(connectionDescriptionObject as ConnectionDescription);
105 | } catch (error) {
106 | setError('Connection Description invalid!');
107 | }
108 | };
109 |
110 | return (
111 |
112 | Select how would you like to start a chat
113 |
114 |
115 | New chat
116 |
117 |
118 |
127 |
128 |
129 | or
130 |
131 |
132 |
133 | );
134 | });
135 |
--------------------------------------------------------------------------------
/example/src/components/PageHeader/PageHeader.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PageHeader = styled.h3`
4 | width: 100%;
5 | font-size: 10px;
6 | font-weight: 400;
7 | text-align: center;
8 | color: white;
9 | background-color: black;
10 | margin: 0;
11 | padding: 4px;
12 | `;
13 |
--------------------------------------------------------------------------------
/example/src/components/Slave/Slave.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, createRef, memo } from 'react';
2 | import styled from 'styled-components';
3 | import copy from 'copy-to-clipboard';
4 |
5 | import { encode } from '../../util/connectionDescriptionEncoding';
6 | import { PageHeader } from '../PageHeader/PageHeader';
7 | import { Button } from '../Button/Button';
8 | import { TextArea } from '../TextArea/TextArea';
9 | import { useChat } from '../../module/useChat/useChat';
10 | import { ConnectionDescription } from '../../module/PeerConnection/PeerConnection';
11 |
12 | const CopyButton = styled(Button)`
13 | width: 70%;
14 | margin-top: 4px;
15 | `;
16 | const StyledTextArea = styled(TextArea)`
17 | width: 70%;
18 | `;
19 | const Instruction = styled.div`
20 | font-size: 10px;
21 | color: black;
22 | margin-bottom: 4px;
23 | `;
24 | const Card = styled.div`
25 | width: 100%;
26 | display: flex;
27 | flex-direction: column;
28 | align-items: center;
29 | padding: 24px;
30 | border: 1px solid black;
31 | border-top: none;
32 | `;
33 | const Container = styled.div`
34 | display: flex;
35 | flex-direction: column;
36 | `;
37 |
38 | export const Slave: FC = memo(function Slave() {
39 | const { localConnectionDescription } = useChat();
40 | const copyTextAreaRef = createRef();
41 |
42 | const encodedConnectionDescription = encode(localConnectionDescription as ConnectionDescription);
43 |
44 | const handleCopyClick = () => {
45 | if (!copyTextAreaRef.current) return;
46 |
47 | copyTextAreaRef.current.select();
48 | copy(encodedConnectionDescription);
49 | };
50 |
51 | return (
52 |
53 | Joining a chat
54 |
55 | Send back this code to your buddy:
56 |
57 | Copy to clipboard
58 |
59 |
60 | );
61 | });
62 |
--------------------------------------------------------------------------------
/example/src/components/Space/Space.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Space = styled.div<{ size: number }>`
4 | padding-top: ${({ size }) => `${size}px`};
5 | `;
6 |
--------------------------------------------------------------------------------
/example/src/components/TextArea/TextArea.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TextArea = styled.textarea`
4 | display: block;
5 | font-size: 10px;
6 | width: 100%;
7 | height: 30px;
8 | border: 1px solid black;
9 | outline: none;
10 | background-image: none;
11 | appearance: none;
12 | background-color: transparent;
13 | box-shadow: none;
14 | `;
15 |
--------------------------------------------------------------------------------
/example/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import * as serviceWorker from './serviceWorker';
5 | import { PeerConnectionProvider } from './module/PeerConnection/PeerConnection';
6 | import { FileBuffersProvider } from './module/FileBuffers/FileBuffers';
7 | import { GlobalStyle } from './styles/globalStyle';
8 | import App from './App';
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ,
19 | document.getElementById('root'),
20 | );
21 |
22 | // If you want your app to work offline and load faster, you can change
23 | // unregister() to register() below. Note this comes with some pitfalls.
24 | // Learn more about service workers: https://bit.ly/CRA-PWA
25 | serviceWorker.unregister();
26 |
--------------------------------------------------------------------------------
/example/src/module/ChatMessages/ChatMessages.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, createContext, FC, useContext, useEffect } from 'react';
2 | import { ReplaySubject } from 'rxjs';
3 |
4 | import { ChatMessageType } from '../../types/ChatMessageType';
5 |
6 | export interface SendFileInfoProps {
7 | fileId: string;
8 | fileName: string;
9 | fileSize: number;
10 | }
11 |
12 | export interface SendFileChunkProps {
13 | fileId: string;
14 | fileChunkIndex: number;
15 | fileChunk: string;
16 | }
17 |
18 | const chatMessagesSubject = new ReplaySubject();
19 |
20 | const ChatMessagesContext = createContext(chatMessagesSubject);
21 |
22 | export const ChatMessagesProvider: FC = ({ children }) => {
23 | return {children};
24 | };
25 |
26 | export const useChatMessages = () => {
27 | const chatMessagesSubject = useContext(ChatMessagesContext);
28 | const [chatMessages, setChatMessages] = useState([]);
29 |
30 | useEffect(() => {
31 | const subscription = chatMessagesSubject.subscribe((chatMessage) => {
32 | setChatMessages((chatMessages) => [...chatMessages, chatMessage]);
33 | });
34 |
35 | return () => subscription.unsubscribe();
36 | }, [chatMessagesSubject, setChatMessages]);
37 |
38 | const sendChatMessage = useCallback(
39 | (chatMessage: ChatMessageType) => {
40 | chatMessagesSubject.next(chatMessage);
41 | },
42 | [chatMessagesSubject],
43 | );
44 |
45 | return { chatMessages, sendChatMessage };
46 | };
47 |
--------------------------------------------------------------------------------
/example/src/module/FileBuffers/FileBuffers.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, FC, useContext, useState, useEffect, useCallback } from 'react';
2 | import { Subject } from 'rxjs';
3 | import { MessagePayloadFileChunkType, MessagePayloadFileInfoType } from '../../types/MessagePayloadType';
4 | import { stringToArrayBuffer } from '../../util/arrayBufferConverter';
5 |
6 | export type FILE_BUFFER_MODES = 'downloading' | 'uploading';
7 |
8 | export type FileBuffer = {
9 | fileId: string;
10 | fileName: string;
11 | fileSize: number;
12 | mode: FILE_BUFFER_MODES;
13 | receivedSize: number;
14 | receivedBuffer: ArrayBuffer[];
15 | receivedBlob?: Blob;
16 | receivedBlobUrl?: string;
17 | };
18 | export type FileBuffersType = { [index: string]: FileBuffer };
19 |
20 | const fileBuffers: FileBuffersType = {};
21 |
22 | export type FileBuffersUpdate = {
23 | fileId: string;
24 | };
25 |
26 | const fileBuffersUpdates = new Subject();
27 |
28 | type FileBuffersContextType = { fileBuffersUpdates: typeof fileBuffersUpdates };
29 |
30 | const contextValue = { fileBuffersUpdates };
31 |
32 | const FileBuffersContext = createContext(contextValue);
33 |
34 | export const FileBuffersProvider: FC = ({ children }) => {
35 | return {children};
36 | };
37 |
38 | export const useOnFileBufferReceived = () => {
39 | const { fileBuffersUpdates } = useContext(FileBuffersContext);
40 |
41 | const onFileInfoUploaded = useCallback(
42 | ({ fileId, fileSize, fileName }: MessagePayloadFileInfoType) => {
43 | const file = fileBuffers[fileId];
44 |
45 | if (!file) {
46 | fileBuffers[fileId] = {
47 | fileId,
48 | fileName,
49 | fileSize,
50 | mode: 'uploading',
51 | receivedSize: 0,
52 | receivedBuffer: [],
53 | };
54 | } else {
55 | file.fileName = fileName;
56 | file.fileSize = fileSize;
57 | }
58 |
59 | fileBuffersUpdates.next({ fileId });
60 | },
61 | [fileBuffersUpdates],
62 | );
63 |
64 | const onFileChunkUploaded = useCallback(
65 | ({ fileId, fileChunk }: MessagePayloadFileChunkType) => {
66 | let file = fileBuffers[fileId];
67 |
68 | if (!file) {
69 | fileBuffers[fileId] = {
70 | fileId,
71 | fileName: '',
72 | fileSize: -1,
73 | mode: 'uploading',
74 | receivedSize: 0,
75 | receivedBuffer: [],
76 | };
77 |
78 | file = fileBuffers[fileId];
79 | }
80 |
81 | const arrayBuffer = stringToArrayBuffer(fileChunk);
82 | file.receivedSize += arrayBuffer.byteLength;
83 |
84 | fileBuffersUpdates.next({ fileId });
85 | },
86 | [fileBuffersUpdates],
87 | );
88 |
89 | const onFileInfoReceived = useCallback(
90 | ({ fileId, fileSize, fileName }: MessagePayloadFileInfoType) => {
91 | const file = fileBuffers[fileId];
92 |
93 | if (!file) {
94 | fileBuffers[fileId] = {
95 | fileId,
96 | fileName,
97 | fileSize,
98 | mode: 'downloading',
99 | receivedSize: 0,
100 | receivedBuffer: [],
101 | };
102 | } else {
103 | file.fileName = fileName;
104 | file.fileSize = fileSize;
105 | }
106 |
107 | fileBuffersUpdates.next({ fileId });
108 | },
109 | [fileBuffersUpdates],
110 | );
111 |
112 | const onFileChunkReceived = useCallback(
113 | ({ fileId, fileChunkIndex, fileChunk }: MessagePayloadFileChunkType) => {
114 | let file = fileBuffers[fileId];
115 |
116 | if (!file) {
117 | fileBuffers[fileId] = {
118 | fileId,
119 | fileName: '',
120 | fileSize: -1,
121 | mode: 'downloading',
122 | receivedSize: 0,
123 | receivedBuffer: [],
124 | };
125 |
126 | file = fileBuffers[fileId];
127 | }
128 |
129 | const arrayBuffer = stringToArrayBuffer(fileChunk);
130 | file.receivedBuffer[fileChunkIndex] = arrayBuffer;
131 | file.receivedSize += arrayBuffer.byteLength;
132 |
133 | if (file.receivedSize === file.fileSize) {
134 | file.receivedBlob = new Blob(file.receivedBuffer);
135 | file.receivedBuffer = [];
136 | file.receivedBlobUrl = URL.createObjectURL(file.receivedBlob);
137 | }
138 |
139 | fileBuffersUpdates.next({ fileId });
140 | },
141 | [fileBuffersUpdates],
142 | );
143 |
144 | return { onFileInfoUploaded, onFileChunkUploaded, onFileInfoReceived, onFileChunkReceived };
145 | };
146 |
147 | export const useFileBuffer = (fileId: string) => {
148 | const { fileBuffersUpdates } = useContext(FileBuffersContext);
149 |
150 | const [fileName, setFileName] = useState(undefined);
151 | const [fileSize, setFileSize] = useState(undefined);
152 | const [mode, setMode] = useState(undefined);
153 | const [receivedSize, setReceivedSize] = useState(0);
154 | const [receivedBlobUrl, setReceivedBlobUrl] = useState(undefined);
155 |
156 | useEffect(() => {
157 | const subscription = fileBuffersUpdates.subscribe((fileBufferUpdate) => {
158 | if (fileBufferUpdate.fileId === fileId) {
159 | const fileBuffer = fileBuffers[fileId] as FileBuffer | undefined;
160 |
161 | if (!fileBuffer) return;
162 |
163 | setFileName(fileBuffer.fileName);
164 | setFileSize(fileBuffer.fileSize);
165 | setMode(fileBuffer.mode);
166 | setReceivedSize(fileBuffer.receivedSize);
167 | setReceivedBlobUrl(fileBuffer.receivedBlobUrl);
168 | }
169 | });
170 |
171 | return () => subscription.unsubscribe();
172 | }, [fileBuffersUpdates, fileId]);
173 |
174 | return { fileName, fileSize, mode, receivedSize, receivedBlobUrl };
175 | };
176 |
--------------------------------------------------------------------------------
/example/src/module/PeerConnection/PeerConnection.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, FC, useState, useRef, useCallback, useEffect, useContext, useMemo } from 'react';
2 | import { Subject } from 'rxjs';
3 |
4 | import { generateKey, decrypt, encrypt } from '../../util/encryption';
5 | import { createPeerConnection, CreatePeerConnectionResponse } from '../../typescript-lib';
6 |
7 | export type ConnectionDescription = {
8 | description: string;
9 | encryptionKey: string;
10 | };
11 | export enum PEER_CONNECTION_MODE {
12 | HOST = 'HOST',
13 | SLAVE = 'SLAVE',
14 | }
15 |
16 | const iceServers: RTCIceServer[] = [
17 | {
18 | urls: 'stun:stun.l.google.com:19302',
19 | },
20 | // {
21 | // urls: 'turn:turn.anyfirewall.com:443?transport=tcp',
22 | // username: 'webrtc',
23 | // credential: 'webrtc',
24 | // },
25 | ];
26 |
27 | const peerConnectionSubject = new Subject();
28 |
29 | interface PeerConnectionContextValue {
30 | mode: PEER_CONNECTION_MODE | undefined;
31 | isConnected: boolean;
32 | localConnectionDescription: ConnectionDescription | undefined;
33 | startAsHost: () => void;
34 | startAsSlave: (connectionDescription: ConnectionDescription) => void;
35 | setRemoteConnectionDescription: (connectionDescription: ConnectionDescription) => void;
36 | sendMessage: (message: any) => void;
37 | peerConnectionSubject: typeof peerConnectionSubject;
38 | }
39 |
40 | const PeerConnectionContext = createContext({} as PeerConnectionContextValue);
41 |
42 | export const PeerConnectionProvider: FC = ({ children }) => {
43 | const [mode, setMode] = useState(undefined);
44 | const encryptionKeyRef = useRef(generateKey());
45 | const [localDescription, setLocalDescription] = useState();
46 | const [isConnected, setIsConnected] = useState(false);
47 | const peerConnectionRef = useRef();
48 |
49 | const onChannelOpen = useCallback(() => setIsConnected(true), [setIsConnected]);
50 |
51 | const onMessageReceived = useCallback((messageString: string) => {
52 | try {
53 | const decryptedMessageString = decrypt(messageString, encryptionKeyRef.current);
54 | const message = JSON.parse(decryptedMessageString);
55 | peerConnectionSubject.next(message);
56 | } catch (error) {
57 | console.error(error);
58 | }
59 | }, []);
60 |
61 | const startAsHost = useCallback(async () => {
62 | if (typeof mode !== 'undefined') return;
63 |
64 | setMode(PEER_CONNECTION_MODE.HOST);
65 | peerConnectionRef.current = await createPeerConnection({
66 | iceServers,
67 | onMessageReceived,
68 | onChannelOpen,
69 | });
70 |
71 | setLocalDescription(Base64.encode(peerConnectionRef.current.localDescription));
72 | }, [mode, setMode, onMessageReceived, onChannelOpen, setLocalDescription]);
73 |
74 | const startAsSlave = useCallback(
75 | async (connectionDescription: ConnectionDescription) => {
76 | if (typeof mode !== 'undefined') return;
77 |
78 | setMode(PEER_CONNECTION_MODE.SLAVE);
79 | encryptionKeyRef.current = connectionDescription.encryptionKey;
80 |
81 | peerConnectionRef.current = await createPeerConnection({
82 | iceServers,
83 | remoteDescription: Base64.decode(connectionDescription.description),
84 | onMessageReceived,
85 | onChannelOpen,
86 | });
87 | console.log('peerConnectionRef.current.localDescription', peerConnectionRef.current.localDescription);
88 | setLocalDescription(Base64.encode(peerConnectionRef.current.localDescription));
89 | },
90 | [mode, setMode, onMessageReceived, onChannelOpen, setLocalDescription],
91 | );
92 |
93 | const setRemoteConnectionDescription = useCallback((connectionDescription: ConnectionDescription) => {
94 | if (!peerConnectionRef.current) return;
95 |
96 | peerConnectionRef.current.setAnswerDescription(Base64.decode(connectionDescription.description));
97 | }, []);
98 |
99 | const sendMessage = useCallback((message) => {
100 | if (!peerConnectionRef.current) return;
101 |
102 | const messageString = JSON.stringify(message);
103 | const encryptedMessageString = encrypt(messageString, encryptionKeyRef.current);
104 |
105 | peerConnectionRef.current.sendMessage(encryptedMessageString);
106 | }, []);
107 |
108 | const localConnectionDescription: ConnectionDescription | undefined = useMemo(
109 | () =>
110 | localDescription && encryptionKeyRef.current
111 | ? {
112 | description: localDescription,
113 | encryptionKey: encryptionKeyRef.current,
114 | }
115 | : undefined,
116 | [localDescription],
117 | );
118 |
119 | return (
120 |
132 | {children}
133 |
134 | );
135 | };
136 |
137 | export const usePeerConnection = () => {
138 | const {
139 | mode,
140 | isConnected,
141 | localConnectionDescription,
142 | startAsHost,
143 | startAsSlave,
144 | setRemoteConnectionDescription,
145 | sendMessage,
146 | } = useContext(PeerConnectionContext);
147 |
148 | return {
149 | mode,
150 | isConnected,
151 | localConnectionDescription,
152 | startAsHost,
153 | startAsSlave,
154 | setRemoteConnectionDescription,
155 | sendMessage: sendMessage as (message: T) => void,
156 | };
157 | };
158 |
159 | export const usePeerConnectionSubscription = (onMessageReceived: (message: T) => void) => {
160 | const { peerConnectionSubject } = useContext(PeerConnectionContext);
161 |
162 | useEffect(() => {
163 | const subscription = (peerConnectionSubject as Subject).subscribe(onMessageReceived);
164 |
165 | return () => subscription.unsubscribe();
166 | }, [peerConnectionSubject, onMessageReceived]);
167 | };
168 |
--------------------------------------------------------------------------------
/example/src/module/useChat/useChat.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import shortid from 'shortid';
3 |
4 | import { MessageType, MessageTextType, MessageFileInfoType, MessageFileChunkType } from '../../types/MessageType';
5 | import { usePeerConnection, usePeerConnectionSubscription } from '../PeerConnection/PeerConnection';
6 | import { useOnFileBufferReceived } from '../FileBuffers/FileBuffers';
7 | import { MESSAGE_SENDER } from '../../types/MessageSenderEnum';
8 | import { MESSAGE_TYPE } from '../../types/MessageTypeEnum';
9 | import { useChatMessages } from '../ChatMessages/ChatMessages';
10 | import { ChatMessageType } from '../../types/ChatMessageType';
11 |
12 | export interface SendFileInfoProps {
13 | fileId: string;
14 | fileName: string;
15 | fileSize: number;
16 | }
17 |
18 | export interface SendFileChunkProps {
19 | fileId: string;
20 | fileChunkIndex: number;
21 | fileChunk: string;
22 | }
23 |
24 | export const useChat = () => {
25 | const { onFileInfoUploaded, onFileChunkUploaded } = useOnFileBufferReceived();
26 | const { chatMessages, sendChatMessage } = useChatMessages();
27 |
28 | const {
29 | mode,
30 | isConnected,
31 | localConnectionDescription,
32 | startAsHost,
33 | startAsSlave,
34 | setRemoteConnectionDescription,
35 | sendMessage,
36 | } = usePeerConnection();
37 |
38 | const sendTextChatMessage = useCallback(
39 | (messageText: string) => {
40 | const message: MessageTextType = {
41 | id: shortid.generate(),
42 | sender: MESSAGE_SENDER.STRANGER,
43 | type: MESSAGE_TYPE.TEXT,
44 | timestamp: +new Date(),
45 | payload: messageText,
46 | };
47 |
48 | sendMessage(message);
49 | sendChatMessage({
50 | id: message.id,
51 | sender: MESSAGE_SENDER.ME,
52 | timestamp: message.timestamp,
53 | text: message.payload,
54 | });
55 | },
56 | [sendMessage, sendChatMessage],
57 | );
58 |
59 | const sendFileInfo = useCallback(
60 | ({ fileId, fileName, fileSize }: SendFileInfoProps) => {
61 | const message: MessageFileInfoType = {
62 | id: shortid.generate(),
63 | sender: MESSAGE_SENDER.STRANGER,
64 | type: MESSAGE_TYPE.FILE_INFO,
65 | timestamp: +new Date(),
66 | payload: {
67 | fileId,
68 | fileName,
69 | fileSize,
70 | },
71 | };
72 |
73 | sendMessage(message);
74 | onFileInfoUploaded(message.payload);
75 | sendChatMessage({
76 | id: message.id,
77 | sender: MESSAGE_SENDER.ME,
78 | timestamp: message.timestamp,
79 | fileId,
80 | });
81 | },
82 | [sendMessage, onFileInfoUploaded, sendChatMessage],
83 | );
84 |
85 | const sendFileChunk = useCallback(
86 | ({ fileId, fileChunkIndex, fileChunk }: SendFileChunkProps) => {
87 | const message: MessageFileChunkType = {
88 | id: shortid.generate(),
89 | sender: MESSAGE_SENDER.STRANGER,
90 | type: MESSAGE_TYPE.FILE_CHUNK,
91 | timestamp: +new Date(),
92 | payload: {
93 | fileId,
94 | fileChunkIndex,
95 | fileChunk,
96 | },
97 | };
98 |
99 | sendMessage(message);
100 | onFileChunkUploaded(message.payload);
101 | },
102 | [sendMessage, onFileChunkUploaded],
103 | );
104 |
105 | return {
106 | mode,
107 | isConnected,
108 | localConnectionDescription,
109 | chatMessages,
110 | startAsHost,
111 | startAsSlave,
112 | setRemoteConnectionDescription,
113 | sendTextChatMessage,
114 | sendFileInfo,
115 | sendFileChunk,
116 | };
117 | };
118 |
119 | // This hook should be used only in one place since it's connecting Chat to PeerConnection
120 | export const useChatPeerConnectionSubscription = () => {
121 | const { onFileInfoReceived, onFileChunkReceived } = useOnFileBufferReceived();
122 | const { sendChatMessage } = useChatMessages();
123 |
124 | const onMessageReceived = useCallback(
125 | (message: MessageType) => {
126 | if (message.type === MESSAGE_TYPE.TEXT) {
127 | sendChatMessage({
128 | id: message.id,
129 | sender: MESSAGE_SENDER.STRANGER,
130 | timestamp: message.timestamp,
131 | text: message.payload,
132 | });
133 | } else if (message.type === MESSAGE_TYPE.FILE_INFO) {
134 | onFileInfoReceived(message.payload);
135 | sendChatMessage({
136 | id: message.id,
137 | sender: MESSAGE_SENDER.STRANGER,
138 | timestamp: message.timestamp,
139 | fileId: message.payload.fileId,
140 | });
141 | } else if (message.type === MESSAGE_TYPE.FILE_CHUNK) {
142 | onFileChunkReceived(message.payload);
143 | }
144 | },
145 | [sendChatMessage, onFileInfoReceived, onFileChunkReceived],
146 | );
147 |
148 | usePeerConnectionSubscription(onMessageReceived);
149 | };
150 |
--------------------------------------------------------------------------------
/example/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/example/src/styles/globalStyle.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | import { normalizeCss } from './normalizeCss';
4 |
5 | export const GlobalStyle = createGlobalStyle`
6 | ${normalizeCss};
7 |
8 | html, body {
9 | font-family: Lucida Console, Courier, monospace;
10 | color: black;
11 | font-size: 10px;
12 | }
13 |
14 | html {
15 | box-sizing: border-box;
16 | }
17 |
18 | *, *:before, *:after {
19 | box-sizing: inherit;
20 | }
21 | `;
22 |
--------------------------------------------------------------------------------
/example/src/styles/normalizeCss.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | export const normalizeCss = css`
4 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
5 |
6 | /* Document
7 | ========================================================================== */
8 |
9 | /**
10 | * 1. Correct the line height in all browsers.
11 | * 2. Prevent adjustments of font size after orientation changes in iOS.
12 | */
13 |
14 | html {
15 | line-height: 1.15; /* 1 */
16 | -webkit-text-size-adjust: 100%; /* 2 */
17 | }
18 |
19 | /* Sections
20 | ========================================================================== */
21 |
22 | /**
23 | * Remove the margin in all browsers.
24 | */
25 |
26 | body {
27 | margin: 0;
28 | }
29 |
30 | /**
31 | * Render the "main" element consistently in IE.
32 | */
33 |
34 | main {
35 | display: block;
36 | }
37 |
38 | /**
39 | * Correct the font size and margin on "h1" elements within "section" and
40 | * "article" contexts in Chrome, Firefox, and Safari.
41 | */
42 |
43 | h1 {
44 | font-size: 2em;
45 | margin: 0.67em 0;
46 | }
47 |
48 | /* Grouping content
49 | ========================================================================== */
50 |
51 | /**
52 | * 1. Add the correct box sizing in Firefox.
53 | * 2. Show the overflow in Edge and IE.
54 | */
55 |
56 | hr {
57 | box-sizing: content-box; /* 1 */
58 | height: 0; /* 1 */
59 | overflow: visible; /* 2 */
60 | }
61 |
62 | /**
63 | * 1. Correct the inheritance and scaling of font size in all browsers.
64 | * 2. Correct the odd "em" font sizing in all browsers.
65 | */
66 |
67 | pre {
68 | font-family: monospace, monospace; /* 1 */
69 | font-size: 1em; /* 2 */
70 | }
71 |
72 | /* Text-level semantics
73 | ========================================================================== */
74 |
75 | /**
76 | * Remove the gray background on active links in IE 10.
77 | */
78 |
79 | a {
80 | background-color: transparent;
81 | }
82 |
83 | /**
84 | * 1. Remove the bottom border in Chrome 57-
85 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
86 | */
87 |
88 | abbr[title] {
89 | border-bottom: none; /* 1 */
90 | text-decoration: underline; /* 2 */
91 | text-decoration: underline dotted; /* 2 */
92 | }
93 |
94 | /**
95 | * Add the correct font weight in Chrome, Edge, and Safari.
96 | */
97 |
98 | b,
99 | strong {
100 | font-weight: bolder;
101 | }
102 |
103 | /**
104 | * 1. Correct the inheritance and scaling of font size in all browsers.
105 | * 2. Correct the odd "em" font sizing in all browsers.
106 | */
107 |
108 | code,
109 | kbd,
110 | samp {
111 | font-family: monospace, monospace; /* 1 */
112 | font-size: 1em; /* 2 */
113 | }
114 |
115 | /**
116 | * Add the correct font size in all browsers.
117 | */
118 |
119 | small {
120 | font-size: 80%;
121 | }
122 |
123 | /**
124 | * Prevent "sub" and "sup" elements from affecting the line height in
125 | * all browsers.
126 | */
127 |
128 | sub,
129 | sup {
130 | font-size: 75%;
131 | line-height: 0;
132 | position: relative;
133 | vertical-align: baseline;
134 | }
135 |
136 | sub {
137 | bottom: -0.25em;
138 | }
139 |
140 | sup {
141 | top: -0.5em;
142 | }
143 |
144 | /* Embedded content
145 | ========================================================================== */
146 |
147 | /**
148 | * Remove the border on images inside links in IE 10.
149 | */
150 |
151 | img {
152 | border-style: none;
153 | }
154 |
155 | /* Forms
156 | ========================================================================== */
157 |
158 | /**
159 | * 1. Change the font styles in all browsers.
160 | * 2. Remove the margin in Firefox and Safari.
161 | */
162 |
163 | button,
164 | input,
165 | optgroup,
166 | select,
167 | textarea {
168 | font-family: inherit; /* 1 */
169 | font-size: 100%; /* 1 */
170 | line-height: 1.15; /* 1 */
171 | margin: 0; /* 2 */
172 | }
173 |
174 | /**
175 | * Show the overflow in IE.
176 | * 1. Show the overflow in Edge.
177 | */
178 |
179 | button,
180 | input {
181 | /* 1 */
182 | overflow: visible;
183 | }
184 |
185 | /**
186 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
187 | * 1. Remove the inheritance of text transform in Firefox.
188 | */
189 |
190 | button,
191 | select {
192 | /* 1 */
193 | text-transform: none;
194 | }
195 |
196 | /**
197 | * Correct the inability to style clickable types in iOS and Safari.
198 | */
199 |
200 | button,
201 | [type='button'],
202 | [type='reset'],
203 | [type='submit'] {
204 | -webkit-appearance: button;
205 | }
206 |
207 | /**
208 | * Remove the inner border and padding in Firefox.
209 | */
210 |
211 | button::-moz-focus-inner,
212 | [type='button']::-moz-focus-inner,
213 | [type='reset']::-moz-focus-inner,
214 | [type='submit']::-moz-focus-inner {
215 | border-style: none;
216 | padding: 0;
217 | }
218 |
219 | /**
220 | * Restore the focus styles unset by the previous rule.
221 | */
222 |
223 | button:-moz-focusring,
224 | [type='button']:-moz-focusring,
225 | [type='reset']:-moz-focusring,
226 | [type='submit']:-moz-focusring {
227 | outline: 1px dotted ButtonText;
228 | }
229 |
230 | /**
231 | * Correct the padding in Firefox.
232 | */
233 |
234 | fieldset {
235 | padding: 0.35em 0.75em 0.625em;
236 | }
237 |
238 | /**
239 | * 1. Correct the text wrapping in Edge and IE.
240 | * 2. Correct the color inheritance from "fieldset" elements in IE.
241 | * 3. Remove the padding so developers are not caught out when they zero out
242 | * "fieldset" elements in all browsers.
243 | */
244 |
245 | legend {
246 | box-sizing: border-box; /* 1 */
247 | color: inherit; /* 2 */
248 | display: table; /* 1 */
249 | max-width: 100%; /* 1 */
250 | padding: 0; /* 3 */
251 | white-space: normal; /* 1 */
252 | }
253 |
254 | /**
255 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
256 | */
257 |
258 | progress {
259 | vertical-align: baseline;
260 | }
261 |
262 | /**
263 | * Remove the default vertical scrollbar in IE 10+.
264 | */
265 |
266 | textarea {
267 | overflow: auto;
268 | }
269 |
270 | /**
271 | * 1. Add the correct box sizing in IE 10.
272 | * 2. Remove the padding in IE 10.
273 | */
274 |
275 | [type='checkbox'],
276 | [type='radio'] {
277 | box-sizing: border-box; /* 1 */
278 | padding: 0; /* 2 */
279 | }
280 |
281 | /**
282 | * Correct the cursor style of increment and decrement buttons in Chrome.
283 | */
284 |
285 | [type='number']::-webkit-inner-spin-button,
286 | [type='number']::-webkit-outer-spin-button {
287 | height: auto;
288 | }
289 |
290 | /**
291 | * 1. Correct the odd appearance in Chrome and Safari.
292 | * 2. Correct the outline style in Safari.
293 | */
294 |
295 | [type='search'] {
296 | -webkit-appearance: textfield; /* 1 */
297 | outline-offset: -2px; /* 2 */
298 | }
299 |
300 | /**
301 | * Remove the inner padding in Chrome and Safari on macOS.
302 | */
303 |
304 | [type='search']::-webkit-search-decoration {
305 | -webkit-appearance: none;
306 | }
307 |
308 | /**
309 | * 1. Correct the inability to style clickable types in iOS and Safari.
310 | * 2. Change font properties to "inherit" in Safari.
311 | */
312 |
313 | ::-webkit-file-upload-button {
314 | -webkit-appearance: button; /* 1 */
315 | font: inherit; /* 2 */
316 | }
317 |
318 | /* Interactive
319 | ========================================================================== */
320 |
321 | /*
322 | * Add the correct display in Edge, IE 10+, and Firefox.
323 | */
324 |
325 | details {
326 | display: block;
327 | }
328 |
329 | /*
330 | * Add the correct display in all browsers.
331 | */
332 |
333 | summary {
334 | display: list-item;
335 | }
336 |
337 | /* Misc
338 | ========================================================================== */
339 |
340 | /**
341 | * Add the correct display in IE 10+.
342 | */
343 |
344 | template {
345 | display: none;
346 | }
347 |
348 | /**
349 | * Add the correct display in IE 10.
350 | */
351 |
352 | [hidden] {
353 | display: none;
354 | }
355 | `;
356 |
--------------------------------------------------------------------------------
/example/src/types/ChatMessageType.ts:
--------------------------------------------------------------------------------
1 | import { MESSAGE_SENDER } from './MessageSenderEnum';
2 |
3 | export type ChatMessageType = {
4 | id: string;
5 | sender: MESSAGE_SENDER;
6 | timestamp: number;
7 | text?: string;
8 | fileId?: string;
9 | };
10 |
--------------------------------------------------------------------------------
/example/src/types/MessagePayloadType.ts:
--------------------------------------------------------------------------------
1 | export type MessagePayloadFileInfoType = {
2 | fileId: string;
3 | fileName: string;
4 | fileSize: number;
5 | };
6 | export type MessagePayloadFileChunkType = {
7 | fileId: string;
8 | fileChunkIndex: number;
9 | fileChunk: string;
10 | };
11 | export type MessagePayloadTextType = string;
12 |
13 | export type MessagePayloadType = MessagePayloadTextType | MessagePayloadFileInfoType | MessagePayloadFileChunkType;
14 |
--------------------------------------------------------------------------------
/example/src/types/MessageSenderEnum.ts:
--------------------------------------------------------------------------------
1 | export enum MESSAGE_SENDER {
2 | ME = 'ME',
3 | STRANGER = 'STRANGER',
4 | }
5 |
--------------------------------------------------------------------------------
/example/src/types/MessageType.ts:
--------------------------------------------------------------------------------
1 | import { MESSAGE_SENDER } from './MessageSenderEnum';
2 | import { MESSAGE_TYPE } from './MessageTypeEnum';
3 | import { MessagePayloadTextType, MessagePayloadFileInfoType, MessagePayloadFileChunkType } from './MessagePayloadType';
4 |
5 | export type MessagePayload = MessagePayloadTextType | MessagePayloadFileInfoType | MessagePayloadFileChunkType;
6 | export type Message = {
7 | id: string;
8 | sender: MESSAGE_SENDER;
9 | type: MESSAGE_TYPE;
10 | timestamp: number;
11 | payload: MessagePayload;
12 | };
13 | export type MessageTextType = Message & {
14 | type: MESSAGE_TYPE.TEXT;
15 | payload: MessagePayloadTextType;
16 | };
17 |
18 | export type MessageFileInfoType = Message & {
19 | type: MESSAGE_TYPE.FILE_INFO;
20 | payload: MessagePayloadFileInfoType;
21 | };
22 |
23 | export type MessageFileChunkType = Message & {
24 | type: MESSAGE_TYPE.FILE_CHUNK;
25 | payload: MessagePayloadFileChunkType;
26 | };
27 |
28 | export type MessageType = MessageTextType | MessageFileInfoType | MessageFileChunkType;
29 |
--------------------------------------------------------------------------------
/example/src/types/MessageTypeEnum.ts:
--------------------------------------------------------------------------------
1 | export enum MESSAGE_TYPE {
2 | TEXT = 'TEXT',
3 | FILE_INFO = 'FILE_INFO',
4 | FILE_CHUNK = 'FILE_CHUNK',
5 | }
6 |
--------------------------------------------------------------------------------
/example/src/util/arrayBufferConverter.ts:
--------------------------------------------------------------------------------
1 | export function arrayBufferToString(buffer: ArrayBuffer): string {
2 | return String.fromCharCode.apply(null, Array.from(new Uint8Array(buffer)));
3 | }
4 |
5 | export function stringToArrayBuffer(str: string): ArrayBuffer {
6 | const stringLength = str.length;
7 | const buffer = new ArrayBuffer(stringLength);
8 | const bufferView = new Uint8Array(buffer);
9 | for (let i = 0; i < stringLength; i++) {
10 | bufferView[i] = str.charCodeAt(i);
11 | }
12 | return buffer;
13 | }
14 |
--------------------------------------------------------------------------------
/example/src/util/connectionDescriptionEncoding.ts:
--------------------------------------------------------------------------------
1 | import { Base64 } from 'js-base64';
2 |
3 | import { ConnectionDescription } from '../module/PeerConnection/PeerConnection';
4 |
5 | export function encode(connectionDescription: ConnectionDescription): string {
6 | return Base64.encode(JSON.stringify(connectionDescription));
7 | }
8 |
9 | export function decode(connectionDescriptionCode: string): ConnectionDescription {
10 | return JSON.parse(Base64.decode(connectionDescriptionCode));
11 | }
12 |
--------------------------------------------------------------------------------
/example/src/util/connectionDescriptionValidator.ts:
--------------------------------------------------------------------------------
1 | import { createValidator } from 'schemat';
2 |
3 | const nonEmptyStringValidator = (d: any) => (typeof d === 'string' && d.length > 0 ? undefined : 'required');
4 |
5 | export const connectionDescriptionValidator = createValidator({
6 | description: nonEmptyStringValidator,
7 | encryptionKey: nonEmptyStringValidator,
8 | });
9 |
--------------------------------------------------------------------------------
/example/src/util/encryption.spec.ts:
--------------------------------------------------------------------------------
1 | import { generateKey, encrypt, decrypt } from './encryption';
2 |
3 | describe('/util/encryption', () => {
4 | describe('generateKey()', () => {
5 | it('returns a random string of 64 characters', () => {
6 | expect(typeof generateKey()).toBe('string');
7 | expect(generateKey().length).toBe(64);
8 | expect(generateKey()).not.toBe(generateKey());
9 | });
10 | });
11 |
12 | describe('encrypt() and decrypt()', () => {
13 | it('encrypt and decrypt successfully', () => {
14 | const message = 'Text that will be encrypted';
15 | const key = 'key';
16 | const encrypted = encrypt(message, key);
17 | const decrypted = decrypt(encrypted, key);
18 | expect(message).not.toBe(encrypted);
19 | expect(decrypted).toBe(message);
20 | });
21 | });
22 |
23 | describe('full flow', () => {
24 | it('generates a key, encrypt and decrypt successfully', () => {
25 | const message = 'Text that will be encrypted';
26 | const key = generateKey();
27 | const encrypted = encrypt(message, key);
28 | const decrypted = decrypt(encrypted, key);
29 | expect(message).not.toBe(encrypted);
30 | expect(decrypted).toBe(message);
31 | });
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/example/src/util/encryption.ts:
--------------------------------------------------------------------------------
1 | import CryptoAES from 'crypto-js/aes';
2 | import CryptoENCUtf8 from 'crypto-js/enc-utf8';
3 | import { lib as CryptoLib } from 'crypto-js';
4 | import CryptoPBKDF2 from 'crypto-js/pbkdf2';
5 |
6 | export function encrypt(string: string, key: string): string {
7 | return CryptoAES.encrypt(string, key).toString();
8 | }
9 |
10 | export function decrypt(string: string, key: string): string {
11 | return CryptoAES.decrypt(string, key).toString(CryptoENCUtf8);
12 | }
13 |
14 | export function generateKey(): string {
15 | const passphrase = CryptoLib.WordArray.random(128 / 8);
16 | const salt = CryptoLib.WordArray.random(128 / 8);
17 | return CryptoPBKDF2(passphrase, salt, { keySize: 256 / 32, iterations: 1000 }).toString();
18 | }
19 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "preserve"
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "p2p-chat",
3 | "version": "3.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/code-frame": {
8 | "version": "7.8.3",
9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
10 | "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
11 | "dev": true,
12 | "requires": {
13 | "@babel/highlight": "^7.8.3"
14 | }
15 | },
16 | "@babel/helper-validator-identifier": {
17 | "version": "7.9.5",
18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
19 | "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==",
20 | "dev": true
21 | },
22 | "@babel/highlight": {
23 | "version": "7.9.0",
24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz",
25 | "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==",
26 | "dev": true,
27 | "requires": {
28 | "@babel/helper-validator-identifier": "^7.9.0",
29 | "chalk": "^2.0.0",
30 | "js-tokens": "^4.0.0"
31 | }
32 | },
33 | "@rollup/pluginutils": {
34 | "version": "3.0.9",
35 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.9.tgz",
36 | "integrity": "sha512-TLZavlfPAZYI7v33wQh4mTP6zojne14yok3DNSLcjoG/Hirxfkonn6icP5rrNWRn8nZsirJBFFpijVOJzkUHDg==",
37 | "dev": true,
38 | "requires": {
39 | "@types/estree": "0.0.39",
40 | "estree-walker": "^1.0.1",
41 | "micromatch": "^4.0.2"
42 | }
43 | },
44 | "@types/chai": {
45 | "version": "4.2.11",
46 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz",
47 | "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==",
48 | "dev": true
49 | },
50 | "@types/color-name": {
51 | "version": "1.1.1",
52 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
53 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
54 | "dev": true
55 | },
56 | "@types/eslint-visitor-keys": {
57 | "version": "1.0.0",
58 | "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
59 | "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
60 | "dev": true
61 | },
62 | "@types/estree": {
63 | "version": "0.0.39",
64 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
65 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
66 | "dev": true
67 | },
68 | "@types/json-schema": {
69 | "version": "7.0.4",
70 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
71 | "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
72 | "dev": true
73 | },
74 | "@types/mocha": {
75 | "version": "7.0.2",
76 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz",
77 | "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==",
78 | "dev": true
79 | },
80 | "@typescript-eslint/eslint-plugin": {
81 | "version": "2.27.0",
82 | "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.27.0.tgz",
83 | "integrity": "sha512-/my+vVHRN7zYgcp0n4z5A6HAK7bvKGBiswaM5zIlOQczsxj/aiD7RcgD+dvVFuwFaGh5+kM7XA6Q6PN0bvb1tw==",
84 | "dev": true,
85 | "requires": {
86 | "@typescript-eslint/experimental-utils": "2.27.0",
87 | "functional-red-black-tree": "^1.0.1",
88 | "regexpp": "^3.0.0",
89 | "tsutils": "^3.17.1"
90 | }
91 | },
92 | "@typescript-eslint/experimental-utils": {
93 | "version": "2.27.0",
94 | "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz",
95 | "integrity": "sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw==",
96 | "dev": true,
97 | "requires": {
98 | "@types/json-schema": "^7.0.3",
99 | "@typescript-eslint/typescript-estree": "2.27.0",
100 | "eslint-scope": "^5.0.0",
101 | "eslint-utils": "^2.0.0"
102 | }
103 | },
104 | "@typescript-eslint/parser": {
105 | "version": "2.27.0",
106 | "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.27.0.tgz",
107 | "integrity": "sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg==",
108 | "dev": true,
109 | "requires": {
110 | "@types/eslint-visitor-keys": "^1.0.0",
111 | "@typescript-eslint/experimental-utils": "2.27.0",
112 | "@typescript-eslint/typescript-estree": "2.27.0",
113 | "eslint-visitor-keys": "^1.1.0"
114 | }
115 | },
116 | "@typescript-eslint/typescript-estree": {
117 | "version": "2.27.0",
118 | "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz",
119 | "integrity": "sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg==",
120 | "dev": true,
121 | "requires": {
122 | "debug": "^4.1.1",
123 | "eslint-visitor-keys": "^1.1.0",
124 | "glob": "^7.1.6",
125 | "is-glob": "^4.0.1",
126 | "lodash": "^4.17.15",
127 | "semver": "^6.3.0",
128 | "tsutils": "^3.17.1"
129 | }
130 | },
131 | "acorn": {
132 | "version": "7.1.1",
133 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
134 | "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
135 | "dev": true
136 | },
137 | "acorn-jsx": {
138 | "version": "5.2.0",
139 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
140 | "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
141 | "dev": true
142 | },
143 | "ajv": {
144 | "version": "6.12.0",
145 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
146 | "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
147 | "dev": true,
148 | "requires": {
149 | "fast-deep-equal": "^3.1.1",
150 | "fast-json-stable-stringify": "^2.0.0",
151 | "json-schema-traverse": "^0.4.1",
152 | "uri-js": "^4.2.2"
153 | }
154 | },
155 | "ansi-colors": {
156 | "version": "3.2.3",
157 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
158 | "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
159 | "dev": true
160 | },
161 | "ansi-escapes": {
162 | "version": "4.3.1",
163 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
164 | "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
165 | "dev": true,
166 | "requires": {
167 | "type-fest": "^0.11.0"
168 | },
169 | "dependencies": {
170 | "type-fest": {
171 | "version": "0.11.0",
172 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
173 | "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
174 | "dev": true
175 | }
176 | }
177 | },
178 | "ansi-regex": {
179 | "version": "4.1.0",
180 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
181 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
182 | "dev": true
183 | },
184 | "ansi-styles": {
185 | "version": "3.2.1",
186 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
187 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
188 | "dev": true,
189 | "requires": {
190 | "color-convert": "^1.9.0"
191 | }
192 | },
193 | "anymatch": {
194 | "version": "3.1.1",
195 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
196 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
197 | "dev": true,
198 | "requires": {
199 | "normalize-path": "^3.0.0",
200 | "picomatch": "^2.0.4"
201 | }
202 | },
203 | "arg": {
204 | "version": "4.1.3",
205 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
206 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
207 | "dev": true
208 | },
209 | "argparse": {
210 | "version": "1.0.10",
211 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
212 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
213 | "dev": true,
214 | "requires": {
215 | "sprintf-js": "~1.0.2"
216 | }
217 | },
218 | "assertion-error": {
219 | "version": "1.1.0",
220 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
221 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
222 | "dev": true
223 | },
224 | "astral-regex": {
225 | "version": "1.0.0",
226 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
227 | "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
228 | "dev": true
229 | },
230 | "balanced-match": {
231 | "version": "1.0.0",
232 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
233 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
234 | "dev": true
235 | },
236 | "binary-extensions": {
237 | "version": "2.0.0",
238 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
239 | "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
240 | "dev": true
241 | },
242 | "brace-expansion": {
243 | "version": "1.1.11",
244 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
245 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
246 | "dev": true,
247 | "requires": {
248 | "balanced-match": "^1.0.0",
249 | "concat-map": "0.0.1"
250 | }
251 | },
252 | "braces": {
253 | "version": "3.0.2",
254 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
255 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
256 | "dev": true,
257 | "requires": {
258 | "fill-range": "^7.0.1"
259 | }
260 | },
261 | "browser-stdout": {
262 | "version": "1.3.1",
263 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
264 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
265 | "dev": true
266 | },
267 | "buffer-from": {
268 | "version": "1.1.1",
269 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
270 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
271 | "dev": true
272 | },
273 | "callsites": {
274 | "version": "3.1.0",
275 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
276 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
277 | "dev": true
278 | },
279 | "camelcase": {
280 | "version": "5.3.1",
281 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
282 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
283 | "dev": true
284 | },
285 | "chai": {
286 | "version": "4.2.0",
287 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
288 | "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
289 | "dev": true,
290 | "requires": {
291 | "assertion-error": "^1.1.0",
292 | "check-error": "^1.0.2",
293 | "deep-eql": "^3.0.1",
294 | "get-func-name": "^2.0.0",
295 | "pathval": "^1.1.0",
296 | "type-detect": "^4.0.5"
297 | }
298 | },
299 | "chalk": {
300 | "version": "2.4.2",
301 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
302 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
303 | "dev": true,
304 | "requires": {
305 | "ansi-styles": "^3.2.1",
306 | "escape-string-regexp": "^1.0.5",
307 | "supports-color": "^5.3.0"
308 | },
309 | "dependencies": {
310 | "supports-color": {
311 | "version": "5.5.0",
312 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
313 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
314 | "dev": true,
315 | "requires": {
316 | "has-flag": "^3.0.0"
317 | }
318 | }
319 | }
320 | },
321 | "chardet": {
322 | "version": "0.7.0",
323 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
324 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
325 | "dev": true
326 | },
327 | "check-error": {
328 | "version": "1.0.2",
329 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
330 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
331 | "dev": true
332 | },
333 | "chokidar": {
334 | "version": "3.3.0",
335 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz",
336 | "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==",
337 | "dev": true,
338 | "requires": {
339 | "anymatch": "~3.1.1",
340 | "braces": "~3.0.2",
341 | "fsevents": "~2.1.1",
342 | "glob-parent": "~5.1.0",
343 | "is-binary-path": "~2.1.0",
344 | "is-glob": "~4.0.1",
345 | "normalize-path": "~3.0.0",
346 | "readdirp": "~3.2.0"
347 | }
348 | },
349 | "cli-cursor": {
350 | "version": "3.1.0",
351 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
352 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
353 | "dev": true,
354 | "requires": {
355 | "restore-cursor": "^3.1.0"
356 | }
357 | },
358 | "cli-width": {
359 | "version": "2.2.0",
360 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
361 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
362 | "dev": true
363 | },
364 | "cliui": {
365 | "version": "5.0.0",
366 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
367 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
368 | "dev": true,
369 | "requires": {
370 | "string-width": "^3.1.0",
371 | "strip-ansi": "^5.2.0",
372 | "wrap-ansi": "^5.1.0"
373 | }
374 | },
375 | "color-convert": {
376 | "version": "1.9.3",
377 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
378 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
379 | "dev": true,
380 | "requires": {
381 | "color-name": "1.1.3"
382 | }
383 | },
384 | "color-name": {
385 | "version": "1.1.3",
386 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
387 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
388 | "dev": true
389 | },
390 | "commondir": {
391 | "version": "1.0.1",
392 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
393 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
394 | "dev": true
395 | },
396 | "concat-map": {
397 | "version": "0.0.1",
398 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
399 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
400 | "dev": true
401 | },
402 | "concurrently": {
403 | "version": "5.1.0",
404 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.1.0.tgz",
405 | "integrity": "sha512-9ViZMu3OOCID3rBgU31mjBftro2chOop0G2u1olq1OuwRBVRw/GxHTg80TVJBUTJfoswMmEUeuOg1g1yu1X2dA==",
406 | "dev": true,
407 | "requires": {
408 | "chalk": "^2.4.2",
409 | "date-fns": "^2.0.1",
410 | "lodash": "^4.17.15",
411 | "read-pkg": "^4.0.1",
412 | "rxjs": "^6.5.2",
413 | "spawn-command": "^0.0.2-1",
414 | "supports-color": "^6.1.0",
415 | "tree-kill": "^1.2.2",
416 | "yargs": "^13.3.0"
417 | }
418 | },
419 | "cross-spawn": {
420 | "version": "6.0.5",
421 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
422 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
423 | "dev": true,
424 | "requires": {
425 | "nice-try": "^1.0.4",
426 | "path-key": "^2.0.1",
427 | "semver": "^5.5.0",
428 | "shebang-command": "^1.2.0",
429 | "which": "^1.2.9"
430 | },
431 | "dependencies": {
432 | "semver": {
433 | "version": "5.7.1",
434 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
435 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
436 | "dev": true
437 | }
438 | }
439 | },
440 | "date-fns": {
441 | "version": "2.12.0",
442 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.12.0.tgz",
443 | "integrity": "sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw==",
444 | "dev": true
445 | },
446 | "debug": {
447 | "version": "4.1.1",
448 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
449 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
450 | "dev": true,
451 | "requires": {
452 | "ms": "^2.1.1"
453 | }
454 | },
455 | "decamelize": {
456 | "version": "1.2.0",
457 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
458 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
459 | "dev": true
460 | },
461 | "deep-eql": {
462 | "version": "3.0.1",
463 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
464 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
465 | "dev": true,
466 | "requires": {
467 | "type-detect": "^4.0.0"
468 | }
469 | },
470 | "deep-is": {
471 | "version": "0.1.3",
472 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
473 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
474 | "dev": true
475 | },
476 | "define-properties": {
477 | "version": "1.1.3",
478 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
479 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
480 | "dev": true,
481 | "requires": {
482 | "object-keys": "^1.0.12"
483 | }
484 | },
485 | "diff": {
486 | "version": "3.5.0",
487 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
488 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
489 | "dev": true
490 | },
491 | "doctrine": {
492 | "version": "3.0.0",
493 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
494 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
495 | "dev": true,
496 | "requires": {
497 | "esutils": "^2.0.2"
498 | }
499 | },
500 | "emoji-regex": {
501 | "version": "7.0.3",
502 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
503 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
504 | "dev": true
505 | },
506 | "error-ex": {
507 | "version": "1.3.2",
508 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
509 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
510 | "dev": true,
511 | "requires": {
512 | "is-arrayish": "^0.2.1"
513 | }
514 | },
515 | "es-abstract": {
516 | "version": "1.17.5",
517 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
518 | "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
519 | "dev": true,
520 | "requires": {
521 | "es-to-primitive": "^1.2.1",
522 | "function-bind": "^1.1.1",
523 | "has": "^1.0.3",
524 | "has-symbols": "^1.0.1",
525 | "is-callable": "^1.1.5",
526 | "is-regex": "^1.0.5",
527 | "object-inspect": "^1.7.0",
528 | "object-keys": "^1.1.1",
529 | "object.assign": "^4.1.0",
530 | "string.prototype.trimleft": "^2.1.1",
531 | "string.prototype.trimright": "^2.1.1"
532 | }
533 | },
534 | "es-to-primitive": {
535 | "version": "1.2.1",
536 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
537 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
538 | "dev": true,
539 | "requires": {
540 | "is-callable": "^1.1.4",
541 | "is-date-object": "^1.0.1",
542 | "is-symbol": "^1.0.2"
543 | }
544 | },
545 | "escape-string-regexp": {
546 | "version": "1.0.5",
547 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
548 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
549 | "dev": true
550 | },
551 | "eslint": {
552 | "version": "6.8.0",
553 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
554 | "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
555 | "dev": true,
556 | "requires": {
557 | "@babel/code-frame": "^7.0.0",
558 | "ajv": "^6.10.0",
559 | "chalk": "^2.1.0",
560 | "cross-spawn": "^6.0.5",
561 | "debug": "^4.0.1",
562 | "doctrine": "^3.0.0",
563 | "eslint-scope": "^5.0.0",
564 | "eslint-utils": "^1.4.3",
565 | "eslint-visitor-keys": "^1.1.0",
566 | "espree": "^6.1.2",
567 | "esquery": "^1.0.1",
568 | "esutils": "^2.0.2",
569 | "file-entry-cache": "^5.0.1",
570 | "functional-red-black-tree": "^1.0.1",
571 | "glob-parent": "^5.0.0",
572 | "globals": "^12.1.0",
573 | "ignore": "^4.0.6",
574 | "import-fresh": "^3.0.0",
575 | "imurmurhash": "^0.1.4",
576 | "inquirer": "^7.0.0",
577 | "is-glob": "^4.0.0",
578 | "js-yaml": "^3.13.1",
579 | "json-stable-stringify-without-jsonify": "^1.0.1",
580 | "levn": "^0.3.0",
581 | "lodash": "^4.17.14",
582 | "minimatch": "^3.0.4",
583 | "mkdirp": "^0.5.1",
584 | "natural-compare": "^1.4.0",
585 | "optionator": "^0.8.3",
586 | "progress": "^2.0.0",
587 | "regexpp": "^2.0.1",
588 | "semver": "^6.1.2",
589 | "strip-ansi": "^5.2.0",
590 | "strip-json-comments": "^3.0.1",
591 | "table": "^5.2.3",
592 | "text-table": "^0.2.0",
593 | "v8-compile-cache": "^2.0.3"
594 | },
595 | "dependencies": {
596 | "eslint-utils": {
597 | "version": "1.4.3",
598 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
599 | "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
600 | "dev": true,
601 | "requires": {
602 | "eslint-visitor-keys": "^1.1.0"
603 | }
604 | },
605 | "regexpp": {
606 | "version": "2.0.1",
607 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
608 | "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
609 | "dev": true
610 | }
611 | }
612 | },
613 | "eslint-config-prettier": {
614 | "version": "6.10.1",
615 | "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz",
616 | "integrity": "sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ==",
617 | "dev": true,
618 | "requires": {
619 | "get-stdin": "^6.0.0"
620 | }
621 | },
622 | "eslint-plugin-prettier": {
623 | "version": "3.1.2",
624 | "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz",
625 | "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==",
626 | "dev": true,
627 | "requires": {
628 | "prettier-linter-helpers": "^1.0.0"
629 | }
630 | },
631 | "eslint-scope": {
632 | "version": "5.0.0",
633 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
634 | "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
635 | "dev": true,
636 | "requires": {
637 | "esrecurse": "^4.1.0",
638 | "estraverse": "^4.1.1"
639 | }
640 | },
641 | "eslint-utils": {
642 | "version": "2.0.0",
643 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
644 | "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
645 | "dev": true,
646 | "requires": {
647 | "eslint-visitor-keys": "^1.1.0"
648 | }
649 | },
650 | "eslint-visitor-keys": {
651 | "version": "1.1.0",
652 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
653 | "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
654 | "dev": true
655 | },
656 | "espree": {
657 | "version": "6.2.1",
658 | "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
659 | "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
660 | "dev": true,
661 | "requires": {
662 | "acorn": "^7.1.1",
663 | "acorn-jsx": "^5.2.0",
664 | "eslint-visitor-keys": "^1.1.0"
665 | }
666 | },
667 | "esprima": {
668 | "version": "4.0.1",
669 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
670 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
671 | "dev": true
672 | },
673 | "esquery": {
674 | "version": "1.2.0",
675 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz",
676 | "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==",
677 | "dev": true,
678 | "requires": {
679 | "estraverse": "^5.0.0"
680 | },
681 | "dependencies": {
682 | "estraverse": {
683 | "version": "5.0.0",
684 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz",
685 | "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==",
686 | "dev": true
687 | }
688 | }
689 | },
690 | "esrecurse": {
691 | "version": "4.2.1",
692 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
693 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
694 | "dev": true,
695 | "requires": {
696 | "estraverse": "^4.1.0"
697 | }
698 | },
699 | "estraverse": {
700 | "version": "4.3.0",
701 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
702 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
703 | "dev": true
704 | },
705 | "estree-walker": {
706 | "version": "1.0.1",
707 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
708 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
709 | "dev": true
710 | },
711 | "esutils": {
712 | "version": "2.0.3",
713 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
714 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
715 | "dev": true
716 | },
717 | "external-editor": {
718 | "version": "3.1.0",
719 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
720 | "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
721 | "dev": true,
722 | "requires": {
723 | "chardet": "^0.7.0",
724 | "iconv-lite": "^0.4.24",
725 | "tmp": "^0.0.33"
726 | }
727 | },
728 | "fast-deep-equal": {
729 | "version": "3.1.1",
730 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
731 | "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
732 | "dev": true
733 | },
734 | "fast-diff": {
735 | "version": "1.2.0",
736 | "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
737 | "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
738 | "dev": true
739 | },
740 | "fast-json-stable-stringify": {
741 | "version": "2.1.0",
742 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
743 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
744 | "dev": true
745 | },
746 | "fast-levenshtein": {
747 | "version": "2.0.6",
748 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
749 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
750 | "dev": true
751 | },
752 | "figures": {
753 | "version": "3.2.0",
754 | "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
755 | "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
756 | "dev": true,
757 | "requires": {
758 | "escape-string-regexp": "^1.0.5"
759 | }
760 | },
761 | "file-entry-cache": {
762 | "version": "5.0.1",
763 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
764 | "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
765 | "dev": true,
766 | "requires": {
767 | "flat-cache": "^2.0.1"
768 | }
769 | },
770 | "fill-range": {
771 | "version": "7.0.1",
772 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
773 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
774 | "dev": true,
775 | "requires": {
776 | "to-regex-range": "^5.0.1"
777 | }
778 | },
779 | "find-cache-dir": {
780 | "version": "3.3.1",
781 | "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
782 | "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
783 | "dev": true,
784 | "requires": {
785 | "commondir": "^1.0.1",
786 | "make-dir": "^3.0.2",
787 | "pkg-dir": "^4.1.0"
788 | }
789 | },
790 | "find-up": {
791 | "version": "3.0.0",
792 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
793 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
794 | "dev": true,
795 | "requires": {
796 | "locate-path": "^3.0.0"
797 | }
798 | },
799 | "flat": {
800 | "version": "4.1.0",
801 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
802 | "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
803 | "dev": true,
804 | "requires": {
805 | "is-buffer": "~2.0.3"
806 | }
807 | },
808 | "flat-cache": {
809 | "version": "2.0.1",
810 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
811 | "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
812 | "dev": true,
813 | "requires": {
814 | "flatted": "^2.0.0",
815 | "rimraf": "2.6.3",
816 | "write": "1.0.3"
817 | }
818 | },
819 | "flatted": {
820 | "version": "2.0.2",
821 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
822 | "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
823 | "dev": true
824 | },
825 | "fs-extra": {
826 | "version": "8.1.0",
827 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
828 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
829 | "dev": true,
830 | "requires": {
831 | "graceful-fs": "^4.2.0",
832 | "jsonfile": "^4.0.0",
833 | "universalify": "^0.1.0"
834 | }
835 | },
836 | "fs.realpath": {
837 | "version": "1.0.0",
838 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
839 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
840 | "dev": true
841 | },
842 | "fsevents": {
843 | "version": "2.1.2",
844 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
845 | "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
846 | "dev": true,
847 | "optional": true
848 | },
849 | "function-bind": {
850 | "version": "1.1.1",
851 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
852 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
853 | "dev": true
854 | },
855 | "functional-red-black-tree": {
856 | "version": "1.0.1",
857 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
858 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
859 | "dev": true
860 | },
861 | "get-caller-file": {
862 | "version": "2.0.5",
863 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
864 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
865 | "dev": true
866 | },
867 | "get-func-name": {
868 | "version": "2.0.0",
869 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
870 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
871 | "dev": true
872 | },
873 | "get-stdin": {
874 | "version": "6.0.0",
875 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
876 | "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
877 | "dev": true
878 | },
879 | "glob": {
880 | "version": "7.1.6",
881 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
882 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
883 | "dev": true,
884 | "requires": {
885 | "fs.realpath": "^1.0.0",
886 | "inflight": "^1.0.4",
887 | "inherits": "2",
888 | "minimatch": "^3.0.4",
889 | "once": "^1.3.0",
890 | "path-is-absolute": "^1.0.0"
891 | }
892 | },
893 | "glob-parent": {
894 | "version": "5.1.1",
895 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
896 | "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
897 | "dev": true,
898 | "requires": {
899 | "is-glob": "^4.0.1"
900 | }
901 | },
902 | "globals": {
903 | "version": "12.4.0",
904 | "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
905 | "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
906 | "dev": true,
907 | "requires": {
908 | "type-fest": "^0.8.1"
909 | }
910 | },
911 | "graceful-fs": {
912 | "version": "4.2.3",
913 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
914 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
915 | "dev": true
916 | },
917 | "growl": {
918 | "version": "1.10.5",
919 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
920 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
921 | "dev": true
922 | },
923 | "has": {
924 | "version": "1.0.3",
925 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
926 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
927 | "dev": true,
928 | "requires": {
929 | "function-bind": "^1.1.1"
930 | }
931 | },
932 | "has-flag": {
933 | "version": "3.0.0",
934 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
935 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
936 | "dev": true
937 | },
938 | "has-symbols": {
939 | "version": "1.0.1",
940 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
941 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
942 | "dev": true
943 | },
944 | "he": {
945 | "version": "1.2.0",
946 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
947 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
948 | "dev": true
949 | },
950 | "hosted-git-info": {
951 | "version": "2.8.8",
952 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
953 | "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
954 | "dev": true
955 | },
956 | "iconv-lite": {
957 | "version": "0.4.24",
958 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
959 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
960 | "dev": true,
961 | "requires": {
962 | "safer-buffer": ">= 2.1.2 < 3"
963 | }
964 | },
965 | "ignore": {
966 | "version": "4.0.6",
967 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
968 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
969 | "dev": true
970 | },
971 | "import-fresh": {
972 | "version": "3.2.1",
973 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
974 | "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
975 | "dev": true,
976 | "requires": {
977 | "parent-module": "^1.0.0",
978 | "resolve-from": "^4.0.0"
979 | }
980 | },
981 | "imurmurhash": {
982 | "version": "0.1.4",
983 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
984 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
985 | "dev": true
986 | },
987 | "inflight": {
988 | "version": "1.0.6",
989 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
990 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
991 | "dev": true,
992 | "requires": {
993 | "once": "^1.3.0",
994 | "wrappy": "1"
995 | }
996 | },
997 | "inherits": {
998 | "version": "2.0.4",
999 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1000 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1001 | "dev": true
1002 | },
1003 | "inquirer": {
1004 | "version": "7.1.0",
1005 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
1006 | "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==",
1007 | "dev": true,
1008 | "requires": {
1009 | "ansi-escapes": "^4.2.1",
1010 | "chalk": "^3.0.0",
1011 | "cli-cursor": "^3.1.0",
1012 | "cli-width": "^2.0.0",
1013 | "external-editor": "^3.0.3",
1014 | "figures": "^3.0.0",
1015 | "lodash": "^4.17.15",
1016 | "mute-stream": "0.0.8",
1017 | "run-async": "^2.4.0",
1018 | "rxjs": "^6.5.3",
1019 | "string-width": "^4.1.0",
1020 | "strip-ansi": "^6.0.0",
1021 | "through": "^2.3.6"
1022 | },
1023 | "dependencies": {
1024 | "ansi-regex": {
1025 | "version": "5.0.0",
1026 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
1027 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
1028 | "dev": true
1029 | },
1030 | "ansi-styles": {
1031 | "version": "4.2.1",
1032 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
1033 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
1034 | "dev": true,
1035 | "requires": {
1036 | "@types/color-name": "^1.1.1",
1037 | "color-convert": "^2.0.1"
1038 | }
1039 | },
1040 | "chalk": {
1041 | "version": "3.0.0",
1042 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
1043 | "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
1044 | "dev": true,
1045 | "requires": {
1046 | "ansi-styles": "^4.1.0",
1047 | "supports-color": "^7.1.0"
1048 | }
1049 | },
1050 | "color-convert": {
1051 | "version": "2.0.1",
1052 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1053 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1054 | "dev": true,
1055 | "requires": {
1056 | "color-name": "~1.1.4"
1057 | }
1058 | },
1059 | "color-name": {
1060 | "version": "1.1.4",
1061 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1062 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
1063 | "dev": true
1064 | },
1065 | "emoji-regex": {
1066 | "version": "8.0.0",
1067 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
1068 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
1069 | "dev": true
1070 | },
1071 | "has-flag": {
1072 | "version": "4.0.0",
1073 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
1074 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
1075 | "dev": true
1076 | },
1077 | "is-fullwidth-code-point": {
1078 | "version": "3.0.0",
1079 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
1080 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
1081 | "dev": true
1082 | },
1083 | "string-width": {
1084 | "version": "4.2.0",
1085 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
1086 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
1087 | "dev": true,
1088 | "requires": {
1089 | "emoji-regex": "^8.0.0",
1090 | "is-fullwidth-code-point": "^3.0.0",
1091 | "strip-ansi": "^6.0.0"
1092 | }
1093 | },
1094 | "strip-ansi": {
1095 | "version": "6.0.0",
1096 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
1097 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
1098 | "dev": true,
1099 | "requires": {
1100 | "ansi-regex": "^5.0.0"
1101 | }
1102 | },
1103 | "supports-color": {
1104 | "version": "7.1.0",
1105 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
1106 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
1107 | "dev": true,
1108 | "requires": {
1109 | "has-flag": "^4.0.0"
1110 | }
1111 | }
1112 | }
1113 | },
1114 | "is-arrayish": {
1115 | "version": "0.2.1",
1116 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
1117 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
1118 | "dev": true
1119 | },
1120 | "is-binary-path": {
1121 | "version": "2.1.0",
1122 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1123 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1124 | "dev": true,
1125 | "requires": {
1126 | "binary-extensions": "^2.0.0"
1127 | }
1128 | },
1129 | "is-buffer": {
1130 | "version": "2.0.4",
1131 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
1132 | "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
1133 | "dev": true
1134 | },
1135 | "is-callable": {
1136 | "version": "1.1.5",
1137 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
1138 | "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
1139 | "dev": true
1140 | },
1141 | "is-date-object": {
1142 | "version": "1.0.2",
1143 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
1144 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
1145 | "dev": true
1146 | },
1147 | "is-extglob": {
1148 | "version": "2.1.1",
1149 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1150 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
1151 | "dev": true
1152 | },
1153 | "is-fullwidth-code-point": {
1154 | "version": "2.0.0",
1155 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
1156 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
1157 | "dev": true
1158 | },
1159 | "is-glob": {
1160 | "version": "4.0.1",
1161 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
1162 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
1163 | "dev": true,
1164 | "requires": {
1165 | "is-extglob": "^2.1.1"
1166 | }
1167 | },
1168 | "is-number": {
1169 | "version": "7.0.0",
1170 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1171 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1172 | "dev": true
1173 | },
1174 | "is-promise": {
1175 | "version": "2.1.0",
1176 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
1177 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
1178 | "dev": true
1179 | },
1180 | "is-regex": {
1181 | "version": "1.0.5",
1182 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
1183 | "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
1184 | "dev": true,
1185 | "requires": {
1186 | "has": "^1.0.3"
1187 | }
1188 | },
1189 | "is-symbol": {
1190 | "version": "1.0.3",
1191 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
1192 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
1193 | "dev": true,
1194 | "requires": {
1195 | "has-symbols": "^1.0.1"
1196 | }
1197 | },
1198 | "isexe": {
1199 | "version": "2.0.0",
1200 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
1201 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
1202 | "dev": true
1203 | },
1204 | "js-tokens": {
1205 | "version": "4.0.0",
1206 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1207 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1208 | "dev": true
1209 | },
1210 | "js-yaml": {
1211 | "version": "3.13.1",
1212 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
1213 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
1214 | "dev": true,
1215 | "requires": {
1216 | "argparse": "^1.0.7",
1217 | "esprima": "^4.0.0"
1218 | }
1219 | },
1220 | "json-parse-better-errors": {
1221 | "version": "1.0.2",
1222 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
1223 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
1224 | "dev": true
1225 | },
1226 | "json-schema-traverse": {
1227 | "version": "0.4.1",
1228 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
1229 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
1230 | "dev": true
1231 | },
1232 | "json-stable-stringify-without-jsonify": {
1233 | "version": "1.0.1",
1234 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
1235 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
1236 | "dev": true
1237 | },
1238 | "jsonfile": {
1239 | "version": "4.0.0",
1240 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
1241 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
1242 | "dev": true,
1243 | "requires": {
1244 | "graceful-fs": "^4.1.6"
1245 | }
1246 | },
1247 | "levn": {
1248 | "version": "0.3.0",
1249 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
1250 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
1251 | "dev": true,
1252 | "requires": {
1253 | "prelude-ls": "~1.1.2",
1254 | "type-check": "~0.3.2"
1255 | }
1256 | },
1257 | "locate-path": {
1258 | "version": "3.0.0",
1259 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
1260 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
1261 | "dev": true,
1262 | "requires": {
1263 | "p-locate": "^3.0.0",
1264 | "path-exists": "^3.0.0"
1265 | }
1266 | },
1267 | "lodash": {
1268 | "version": "4.17.15",
1269 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
1270 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
1271 | "dev": true
1272 | },
1273 | "log-symbols": {
1274 | "version": "3.0.0",
1275 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
1276 | "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
1277 | "dev": true,
1278 | "requires": {
1279 | "chalk": "^2.4.2"
1280 | }
1281 | },
1282 | "make-dir": {
1283 | "version": "3.0.2",
1284 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz",
1285 | "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==",
1286 | "dev": true,
1287 | "requires": {
1288 | "semver": "^6.0.0"
1289 | }
1290 | },
1291 | "make-error": {
1292 | "version": "1.3.6",
1293 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
1294 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
1295 | "dev": true
1296 | },
1297 | "micromatch": {
1298 | "version": "4.0.2",
1299 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
1300 | "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
1301 | "dev": true,
1302 | "requires": {
1303 | "braces": "^3.0.1",
1304 | "picomatch": "^2.0.5"
1305 | }
1306 | },
1307 | "mimic-fn": {
1308 | "version": "2.1.0",
1309 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
1310 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
1311 | "dev": true
1312 | },
1313 | "minimatch": {
1314 | "version": "3.0.4",
1315 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
1316 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
1317 | "dev": true,
1318 | "requires": {
1319 | "brace-expansion": "^1.1.7"
1320 | }
1321 | },
1322 | "minimist": {
1323 | "version": "1.2.5",
1324 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
1325 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
1326 | "dev": true
1327 | },
1328 | "mkdirp": {
1329 | "version": "0.5.5",
1330 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
1331 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
1332 | "dev": true,
1333 | "requires": {
1334 | "minimist": "^1.2.5"
1335 | }
1336 | },
1337 | "mocha": {
1338 | "version": "7.1.1",
1339 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz",
1340 | "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==",
1341 | "dev": true,
1342 | "requires": {
1343 | "ansi-colors": "3.2.3",
1344 | "browser-stdout": "1.3.1",
1345 | "chokidar": "3.3.0",
1346 | "debug": "3.2.6",
1347 | "diff": "3.5.0",
1348 | "escape-string-regexp": "1.0.5",
1349 | "find-up": "3.0.0",
1350 | "glob": "7.1.3",
1351 | "growl": "1.10.5",
1352 | "he": "1.2.0",
1353 | "js-yaml": "3.13.1",
1354 | "log-symbols": "3.0.0",
1355 | "minimatch": "3.0.4",
1356 | "mkdirp": "0.5.3",
1357 | "ms": "2.1.1",
1358 | "node-environment-flags": "1.0.6",
1359 | "object.assign": "4.1.0",
1360 | "strip-json-comments": "2.0.1",
1361 | "supports-color": "6.0.0",
1362 | "which": "1.3.1",
1363 | "wide-align": "1.1.3",
1364 | "yargs": "13.3.2",
1365 | "yargs-parser": "13.1.2",
1366 | "yargs-unparser": "1.6.0"
1367 | },
1368 | "dependencies": {
1369 | "debug": {
1370 | "version": "3.2.6",
1371 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
1372 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
1373 | "dev": true,
1374 | "requires": {
1375 | "ms": "^2.1.1"
1376 | }
1377 | },
1378 | "glob": {
1379 | "version": "7.1.3",
1380 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
1381 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
1382 | "dev": true,
1383 | "requires": {
1384 | "fs.realpath": "^1.0.0",
1385 | "inflight": "^1.0.4",
1386 | "inherits": "2",
1387 | "minimatch": "^3.0.4",
1388 | "once": "^1.3.0",
1389 | "path-is-absolute": "^1.0.0"
1390 | }
1391 | },
1392 | "mkdirp": {
1393 | "version": "0.5.3",
1394 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz",
1395 | "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==",
1396 | "dev": true,
1397 | "requires": {
1398 | "minimist": "^1.2.5"
1399 | }
1400 | },
1401 | "ms": {
1402 | "version": "2.1.1",
1403 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
1404 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
1405 | "dev": true
1406 | },
1407 | "strip-json-comments": {
1408 | "version": "2.0.1",
1409 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
1410 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
1411 | "dev": true
1412 | },
1413 | "supports-color": {
1414 | "version": "6.0.0",
1415 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
1416 | "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
1417 | "dev": true,
1418 | "requires": {
1419 | "has-flag": "^3.0.0"
1420 | }
1421 | }
1422 | }
1423 | },
1424 | "ms": {
1425 | "version": "2.1.2",
1426 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1427 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1428 | "dev": true
1429 | },
1430 | "mute-stream": {
1431 | "version": "0.0.8",
1432 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
1433 | "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
1434 | "dev": true
1435 | },
1436 | "natural-compare": {
1437 | "version": "1.4.0",
1438 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
1439 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
1440 | "dev": true
1441 | },
1442 | "nice-try": {
1443 | "version": "1.0.5",
1444 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
1445 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
1446 | "dev": true
1447 | },
1448 | "node-environment-flags": {
1449 | "version": "1.0.6",
1450 | "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
1451 | "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==",
1452 | "dev": true,
1453 | "requires": {
1454 | "object.getownpropertydescriptors": "^2.0.3",
1455 | "semver": "^5.7.0"
1456 | },
1457 | "dependencies": {
1458 | "semver": {
1459 | "version": "5.7.1",
1460 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1461 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
1462 | "dev": true
1463 | }
1464 | }
1465 | },
1466 | "normalize-package-data": {
1467 | "version": "2.5.0",
1468 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
1469 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
1470 | "dev": true,
1471 | "requires": {
1472 | "hosted-git-info": "^2.1.4",
1473 | "resolve": "^1.10.0",
1474 | "semver": "2 || 3 || 4 || 5",
1475 | "validate-npm-package-license": "^3.0.1"
1476 | },
1477 | "dependencies": {
1478 | "semver": {
1479 | "version": "5.7.1",
1480 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1481 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
1482 | "dev": true
1483 | }
1484 | }
1485 | },
1486 | "normalize-path": {
1487 | "version": "3.0.0",
1488 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1489 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1490 | "dev": true
1491 | },
1492 | "object-inspect": {
1493 | "version": "1.7.0",
1494 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
1495 | "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
1496 | "dev": true
1497 | },
1498 | "object-keys": {
1499 | "version": "1.1.1",
1500 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
1501 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
1502 | "dev": true
1503 | },
1504 | "object.assign": {
1505 | "version": "4.1.0",
1506 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
1507 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
1508 | "dev": true,
1509 | "requires": {
1510 | "define-properties": "^1.1.2",
1511 | "function-bind": "^1.1.1",
1512 | "has-symbols": "^1.0.0",
1513 | "object-keys": "^1.0.11"
1514 | }
1515 | },
1516 | "object.getownpropertydescriptors": {
1517 | "version": "2.1.0",
1518 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz",
1519 | "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==",
1520 | "dev": true,
1521 | "requires": {
1522 | "define-properties": "^1.1.3",
1523 | "es-abstract": "^1.17.0-next.1"
1524 | }
1525 | },
1526 | "once": {
1527 | "version": "1.4.0",
1528 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1529 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1530 | "dev": true,
1531 | "requires": {
1532 | "wrappy": "1"
1533 | }
1534 | },
1535 | "onetime": {
1536 | "version": "5.1.0",
1537 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
1538 | "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
1539 | "dev": true,
1540 | "requires": {
1541 | "mimic-fn": "^2.1.0"
1542 | }
1543 | },
1544 | "optionator": {
1545 | "version": "0.8.3",
1546 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
1547 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
1548 | "dev": true,
1549 | "requires": {
1550 | "deep-is": "~0.1.3",
1551 | "fast-levenshtein": "~2.0.6",
1552 | "levn": "~0.3.0",
1553 | "prelude-ls": "~1.1.2",
1554 | "type-check": "~0.3.2",
1555 | "word-wrap": "~1.2.3"
1556 | }
1557 | },
1558 | "os-tmpdir": {
1559 | "version": "1.0.2",
1560 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
1561 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
1562 | "dev": true
1563 | },
1564 | "p-limit": {
1565 | "version": "2.3.0",
1566 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
1567 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
1568 | "dev": true,
1569 | "requires": {
1570 | "p-try": "^2.0.0"
1571 | }
1572 | },
1573 | "p-locate": {
1574 | "version": "3.0.0",
1575 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
1576 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
1577 | "dev": true,
1578 | "requires": {
1579 | "p-limit": "^2.0.0"
1580 | }
1581 | },
1582 | "p-try": {
1583 | "version": "2.2.0",
1584 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
1585 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
1586 | "dev": true
1587 | },
1588 | "parent-module": {
1589 | "version": "1.0.1",
1590 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
1591 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
1592 | "dev": true,
1593 | "requires": {
1594 | "callsites": "^3.0.0"
1595 | }
1596 | },
1597 | "parse-json": {
1598 | "version": "4.0.0",
1599 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
1600 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
1601 | "dev": true,
1602 | "requires": {
1603 | "error-ex": "^1.3.1",
1604 | "json-parse-better-errors": "^1.0.1"
1605 | }
1606 | },
1607 | "path-exists": {
1608 | "version": "3.0.0",
1609 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
1610 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
1611 | "dev": true
1612 | },
1613 | "path-is-absolute": {
1614 | "version": "1.0.1",
1615 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1616 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
1617 | "dev": true
1618 | },
1619 | "path-key": {
1620 | "version": "2.0.1",
1621 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
1622 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
1623 | "dev": true
1624 | },
1625 | "path-parse": {
1626 | "version": "1.0.6",
1627 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
1628 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
1629 | "dev": true
1630 | },
1631 | "pathval": {
1632 | "version": "1.1.0",
1633 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
1634 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
1635 | "dev": true
1636 | },
1637 | "picomatch": {
1638 | "version": "2.2.2",
1639 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
1640 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
1641 | "dev": true
1642 | },
1643 | "pify": {
1644 | "version": "3.0.0",
1645 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
1646 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
1647 | "dev": true
1648 | },
1649 | "pkg-dir": {
1650 | "version": "4.2.0",
1651 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
1652 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
1653 | "dev": true,
1654 | "requires": {
1655 | "find-up": "^4.0.0"
1656 | },
1657 | "dependencies": {
1658 | "find-up": {
1659 | "version": "4.1.0",
1660 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
1661 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
1662 | "dev": true,
1663 | "requires": {
1664 | "locate-path": "^5.0.0",
1665 | "path-exists": "^4.0.0"
1666 | }
1667 | },
1668 | "locate-path": {
1669 | "version": "5.0.0",
1670 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
1671 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
1672 | "dev": true,
1673 | "requires": {
1674 | "p-locate": "^4.1.0"
1675 | }
1676 | },
1677 | "p-locate": {
1678 | "version": "4.1.0",
1679 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
1680 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
1681 | "dev": true,
1682 | "requires": {
1683 | "p-limit": "^2.2.0"
1684 | }
1685 | },
1686 | "path-exists": {
1687 | "version": "4.0.0",
1688 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
1689 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
1690 | "dev": true
1691 | }
1692 | }
1693 | },
1694 | "prelude-ls": {
1695 | "version": "1.1.2",
1696 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
1697 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
1698 | "dev": true
1699 | },
1700 | "prettier": {
1701 | "version": "2.0.4",
1702 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz",
1703 | "integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==",
1704 | "dev": true
1705 | },
1706 | "prettier-linter-helpers": {
1707 | "version": "1.0.0",
1708 | "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
1709 | "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
1710 | "dev": true,
1711 | "requires": {
1712 | "fast-diff": "^1.1.2"
1713 | }
1714 | },
1715 | "progress": {
1716 | "version": "2.0.3",
1717 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
1718 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
1719 | "dev": true
1720 | },
1721 | "punycode": {
1722 | "version": "2.1.1",
1723 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1724 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
1725 | "dev": true
1726 | },
1727 | "read-pkg": {
1728 | "version": "4.0.1",
1729 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
1730 | "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
1731 | "dev": true,
1732 | "requires": {
1733 | "normalize-package-data": "^2.3.2",
1734 | "parse-json": "^4.0.0",
1735 | "pify": "^3.0.0"
1736 | }
1737 | },
1738 | "readdirp": {
1739 | "version": "3.2.0",
1740 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz",
1741 | "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==",
1742 | "dev": true,
1743 | "requires": {
1744 | "picomatch": "^2.0.4"
1745 | }
1746 | },
1747 | "regexpp": {
1748 | "version": "3.1.0",
1749 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
1750 | "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
1751 | "dev": true
1752 | },
1753 | "require-directory": {
1754 | "version": "2.1.1",
1755 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
1756 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
1757 | "dev": true
1758 | },
1759 | "require-main-filename": {
1760 | "version": "2.0.0",
1761 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
1762 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
1763 | "dev": true
1764 | },
1765 | "resolve": {
1766 | "version": "1.15.1",
1767 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
1768 | "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
1769 | "dev": true,
1770 | "requires": {
1771 | "path-parse": "^1.0.6"
1772 | }
1773 | },
1774 | "resolve-from": {
1775 | "version": "4.0.0",
1776 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
1777 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
1778 | "dev": true
1779 | },
1780 | "restore-cursor": {
1781 | "version": "3.1.0",
1782 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
1783 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
1784 | "dev": true,
1785 | "requires": {
1786 | "onetime": "^5.1.0",
1787 | "signal-exit": "^3.0.2"
1788 | }
1789 | },
1790 | "rimraf": {
1791 | "version": "2.6.3",
1792 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
1793 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
1794 | "dev": true,
1795 | "requires": {
1796 | "glob": "^7.1.3"
1797 | }
1798 | },
1799 | "rollup": {
1800 | "version": "2.6.0",
1801 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.6.0.tgz",
1802 | "integrity": "sha512-qbvQ9ZbvbhBdtRBZ/A4g+9z3iJQ1rHAtjinn3FiN+j5tfz8xiNyTE1JEEMcFWqlH7+NHadI9ieeqKdp8HwYLnQ==",
1803 | "dev": true,
1804 | "requires": {
1805 | "fsevents": "~2.1.2"
1806 | }
1807 | },
1808 | "rollup-plugin-typescript2": {
1809 | "version": "0.27.0",
1810 | "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.0.tgz",
1811 | "integrity": "sha512-SRKG/Canve3cxBsqhY1apIBznqnX9X/WU3Lrq3XSwmTmFqccj3+//logLXFEmp+PYFNllSVng+f4zjqRTPKNkA==",
1812 | "dev": true,
1813 | "requires": {
1814 | "@rollup/pluginutils": "^3.0.8",
1815 | "find-cache-dir": "^3.3.1",
1816 | "fs-extra": "8.1.0",
1817 | "resolve": "1.15.1",
1818 | "tslib": "1.11.1"
1819 | }
1820 | },
1821 | "run-async": {
1822 | "version": "2.4.0",
1823 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz",
1824 | "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==",
1825 | "dev": true,
1826 | "requires": {
1827 | "is-promise": "^2.1.0"
1828 | }
1829 | },
1830 | "rxjs": {
1831 | "version": "6.5.5",
1832 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
1833 | "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
1834 | "dev": true,
1835 | "requires": {
1836 | "tslib": "^1.9.0"
1837 | }
1838 | },
1839 | "safer-buffer": {
1840 | "version": "2.1.2",
1841 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1842 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1843 | "dev": true
1844 | },
1845 | "semver": {
1846 | "version": "6.3.0",
1847 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
1848 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
1849 | "dev": true
1850 | },
1851 | "set-blocking": {
1852 | "version": "2.0.0",
1853 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
1854 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
1855 | "dev": true
1856 | },
1857 | "shebang-command": {
1858 | "version": "1.2.0",
1859 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
1860 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
1861 | "dev": true,
1862 | "requires": {
1863 | "shebang-regex": "^1.0.0"
1864 | }
1865 | },
1866 | "shebang-regex": {
1867 | "version": "1.0.0",
1868 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
1869 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
1870 | "dev": true
1871 | },
1872 | "signal-exit": {
1873 | "version": "3.0.3",
1874 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
1875 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
1876 | "dev": true
1877 | },
1878 | "slice-ansi": {
1879 | "version": "2.1.0",
1880 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
1881 | "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
1882 | "dev": true,
1883 | "requires": {
1884 | "ansi-styles": "^3.2.0",
1885 | "astral-regex": "^1.0.0",
1886 | "is-fullwidth-code-point": "^2.0.0"
1887 | }
1888 | },
1889 | "source-map": {
1890 | "version": "0.6.1",
1891 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1892 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1893 | "dev": true
1894 | },
1895 | "source-map-support": {
1896 | "version": "0.5.16",
1897 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
1898 | "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
1899 | "dev": true,
1900 | "requires": {
1901 | "buffer-from": "^1.0.0",
1902 | "source-map": "^0.6.0"
1903 | }
1904 | },
1905 | "spawn-command": {
1906 | "version": "0.0.2-1",
1907 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
1908 | "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
1909 | "dev": true
1910 | },
1911 | "spdx-correct": {
1912 | "version": "3.1.0",
1913 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
1914 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
1915 | "dev": true,
1916 | "requires": {
1917 | "spdx-expression-parse": "^3.0.0",
1918 | "spdx-license-ids": "^3.0.0"
1919 | }
1920 | },
1921 | "spdx-exceptions": {
1922 | "version": "2.2.0",
1923 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
1924 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
1925 | "dev": true
1926 | },
1927 | "spdx-expression-parse": {
1928 | "version": "3.0.0",
1929 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
1930 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
1931 | "dev": true,
1932 | "requires": {
1933 | "spdx-exceptions": "^2.1.0",
1934 | "spdx-license-ids": "^3.0.0"
1935 | }
1936 | },
1937 | "spdx-license-ids": {
1938 | "version": "3.0.5",
1939 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
1940 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
1941 | "dev": true
1942 | },
1943 | "sprintf-js": {
1944 | "version": "1.0.3",
1945 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
1946 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
1947 | "dev": true
1948 | },
1949 | "string-width": {
1950 | "version": "3.1.0",
1951 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
1952 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
1953 | "dev": true,
1954 | "requires": {
1955 | "emoji-regex": "^7.0.1",
1956 | "is-fullwidth-code-point": "^2.0.0",
1957 | "strip-ansi": "^5.1.0"
1958 | }
1959 | },
1960 | "string.prototype.trimend": {
1961 | "version": "1.0.1",
1962 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
1963 | "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
1964 | "dev": true,
1965 | "requires": {
1966 | "define-properties": "^1.1.3",
1967 | "es-abstract": "^1.17.5"
1968 | }
1969 | },
1970 | "string.prototype.trimleft": {
1971 | "version": "2.1.2",
1972 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
1973 | "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
1974 | "dev": true,
1975 | "requires": {
1976 | "define-properties": "^1.1.3",
1977 | "es-abstract": "^1.17.5",
1978 | "string.prototype.trimstart": "^1.0.0"
1979 | }
1980 | },
1981 | "string.prototype.trimright": {
1982 | "version": "2.1.2",
1983 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
1984 | "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
1985 | "dev": true,
1986 | "requires": {
1987 | "define-properties": "^1.1.3",
1988 | "es-abstract": "^1.17.5",
1989 | "string.prototype.trimend": "^1.0.0"
1990 | }
1991 | },
1992 | "string.prototype.trimstart": {
1993 | "version": "1.0.1",
1994 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
1995 | "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
1996 | "dev": true,
1997 | "requires": {
1998 | "define-properties": "^1.1.3",
1999 | "es-abstract": "^1.17.5"
2000 | }
2001 | },
2002 | "strip-ansi": {
2003 | "version": "5.2.0",
2004 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
2005 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
2006 | "dev": true,
2007 | "requires": {
2008 | "ansi-regex": "^4.1.0"
2009 | }
2010 | },
2011 | "strip-json-comments": {
2012 | "version": "3.1.0",
2013 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
2014 | "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==",
2015 | "dev": true
2016 | },
2017 | "supports-color": {
2018 | "version": "6.1.0",
2019 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
2020 | "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
2021 | "dev": true,
2022 | "requires": {
2023 | "has-flag": "^3.0.0"
2024 | }
2025 | },
2026 | "table": {
2027 | "version": "5.4.6",
2028 | "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
2029 | "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
2030 | "dev": true,
2031 | "requires": {
2032 | "ajv": "^6.10.2",
2033 | "lodash": "^4.17.14",
2034 | "slice-ansi": "^2.1.0",
2035 | "string-width": "^3.0.0"
2036 | }
2037 | },
2038 | "text-table": {
2039 | "version": "0.2.0",
2040 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
2041 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
2042 | "dev": true
2043 | },
2044 | "through": {
2045 | "version": "2.3.8",
2046 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
2047 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
2048 | "dev": true
2049 | },
2050 | "tmp": {
2051 | "version": "0.0.33",
2052 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
2053 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
2054 | "dev": true,
2055 | "requires": {
2056 | "os-tmpdir": "~1.0.2"
2057 | }
2058 | },
2059 | "to-regex-range": {
2060 | "version": "5.0.1",
2061 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
2062 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
2063 | "dev": true,
2064 | "requires": {
2065 | "is-number": "^7.0.0"
2066 | }
2067 | },
2068 | "tree-kill": {
2069 | "version": "1.2.2",
2070 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
2071 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
2072 | "dev": true
2073 | },
2074 | "ts-node": {
2075 | "version": "8.8.2",
2076 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz",
2077 | "integrity": "sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==",
2078 | "dev": true,
2079 | "requires": {
2080 | "arg": "^4.1.0",
2081 | "diff": "^4.0.1",
2082 | "make-error": "^1.1.1",
2083 | "source-map-support": "^0.5.6",
2084 | "yn": "3.1.1"
2085 | },
2086 | "dependencies": {
2087 | "diff": {
2088 | "version": "4.0.2",
2089 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
2090 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
2091 | "dev": true
2092 | }
2093 | }
2094 | },
2095 | "tslib": {
2096 | "version": "1.11.1",
2097 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
2098 | "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
2099 | "dev": true
2100 | },
2101 | "tsutils": {
2102 | "version": "3.17.1",
2103 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
2104 | "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
2105 | "dev": true,
2106 | "requires": {
2107 | "tslib": "^1.8.1"
2108 | }
2109 | },
2110 | "type-check": {
2111 | "version": "0.3.2",
2112 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
2113 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
2114 | "dev": true,
2115 | "requires": {
2116 | "prelude-ls": "~1.1.2"
2117 | }
2118 | },
2119 | "type-detect": {
2120 | "version": "4.0.8",
2121 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
2122 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
2123 | "dev": true
2124 | },
2125 | "type-fest": {
2126 | "version": "0.8.1",
2127 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
2128 | "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
2129 | "dev": true
2130 | },
2131 | "typescript": {
2132 | "version": "3.8.3",
2133 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
2134 | "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
2135 | "dev": true
2136 | },
2137 | "universalify": {
2138 | "version": "0.1.2",
2139 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
2140 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
2141 | "dev": true
2142 | },
2143 | "uri-js": {
2144 | "version": "4.2.2",
2145 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
2146 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
2147 | "dev": true,
2148 | "requires": {
2149 | "punycode": "^2.1.0"
2150 | }
2151 | },
2152 | "v8-compile-cache": {
2153 | "version": "2.1.0",
2154 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
2155 | "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
2156 | "dev": true
2157 | },
2158 | "validate-npm-package-license": {
2159 | "version": "3.0.4",
2160 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
2161 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
2162 | "dev": true,
2163 | "requires": {
2164 | "spdx-correct": "^3.0.0",
2165 | "spdx-expression-parse": "^3.0.0"
2166 | }
2167 | },
2168 | "which": {
2169 | "version": "1.3.1",
2170 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
2171 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
2172 | "dev": true,
2173 | "requires": {
2174 | "isexe": "^2.0.0"
2175 | }
2176 | },
2177 | "which-module": {
2178 | "version": "2.0.0",
2179 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
2180 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
2181 | "dev": true
2182 | },
2183 | "wide-align": {
2184 | "version": "1.1.3",
2185 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
2186 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
2187 | "dev": true,
2188 | "requires": {
2189 | "string-width": "^1.0.2 || 2"
2190 | },
2191 | "dependencies": {
2192 | "ansi-regex": {
2193 | "version": "3.0.0",
2194 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
2195 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
2196 | "dev": true
2197 | },
2198 | "string-width": {
2199 | "version": "2.1.1",
2200 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
2201 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
2202 | "dev": true,
2203 | "requires": {
2204 | "is-fullwidth-code-point": "^2.0.0",
2205 | "strip-ansi": "^4.0.0"
2206 | }
2207 | },
2208 | "strip-ansi": {
2209 | "version": "4.0.0",
2210 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
2211 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
2212 | "dev": true,
2213 | "requires": {
2214 | "ansi-regex": "^3.0.0"
2215 | }
2216 | }
2217 | }
2218 | },
2219 | "word-wrap": {
2220 | "version": "1.2.3",
2221 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
2222 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
2223 | "dev": true
2224 | },
2225 | "wrap-ansi": {
2226 | "version": "5.1.0",
2227 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
2228 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
2229 | "dev": true,
2230 | "requires": {
2231 | "ansi-styles": "^3.2.0",
2232 | "string-width": "^3.0.0",
2233 | "strip-ansi": "^5.0.0"
2234 | }
2235 | },
2236 | "wrappy": {
2237 | "version": "1.0.2",
2238 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
2239 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
2240 | "dev": true
2241 | },
2242 | "write": {
2243 | "version": "1.0.3",
2244 | "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
2245 | "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
2246 | "dev": true,
2247 | "requires": {
2248 | "mkdirp": "^0.5.1"
2249 | }
2250 | },
2251 | "y18n": {
2252 | "version": "4.0.0",
2253 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
2254 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
2255 | "dev": true
2256 | },
2257 | "yargs": {
2258 | "version": "13.3.2",
2259 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
2260 | "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
2261 | "dev": true,
2262 | "requires": {
2263 | "cliui": "^5.0.0",
2264 | "find-up": "^3.0.0",
2265 | "get-caller-file": "^2.0.1",
2266 | "require-directory": "^2.1.1",
2267 | "require-main-filename": "^2.0.0",
2268 | "set-blocking": "^2.0.0",
2269 | "string-width": "^3.0.0",
2270 | "which-module": "^2.0.0",
2271 | "y18n": "^4.0.0",
2272 | "yargs-parser": "^13.1.2"
2273 | }
2274 | },
2275 | "yargs-parser": {
2276 | "version": "13.1.2",
2277 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
2278 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
2279 | "dev": true,
2280 | "requires": {
2281 | "camelcase": "^5.0.0",
2282 | "decamelize": "^1.2.0"
2283 | }
2284 | },
2285 | "yargs-unparser": {
2286 | "version": "1.6.0",
2287 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
2288 | "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
2289 | "dev": true,
2290 | "requires": {
2291 | "flat": "^4.1.0",
2292 | "lodash": "^4.17.15",
2293 | "yargs": "^13.3.0"
2294 | }
2295 | },
2296 | "yn": {
2297 | "version": "3.1.1",
2298 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
2299 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
2300 | "dev": true
2301 | }
2302 | }
2303 | }
2304 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "p2p-chat",
3 | "version": "3.0.0",
4 | "main": "dist/index.cjs.js",
5 | "module": "dist/index.esm.js",
6 | "repository": {
7 | "url": "https://github.com/michal-wrzosek/p2p-chat",
8 | "type": "git"
9 | },
10 | "license": "MIT",
11 | "types": "dist/index.d.ts",
12 | "devDependencies": {
13 | "@types/chai": "^4.2.11",
14 | "@types/mocha": "^7.0.2",
15 | "@typescript-eslint/eslint-plugin": "^2.27.0",
16 | "@typescript-eslint/parser": "^2.27.0",
17 | "chai": "^4.2.0",
18 | "concurrently": "^5.1.0",
19 | "eslint": "^6.8.0",
20 | "eslint-config-prettier": "^6.10.1",
21 | "eslint-plugin-prettier": "^3.1.2",
22 | "mocha": "^7.1.1",
23 | "prettier": "^2.0.4",
24 | "rollup": "^2.6.0",
25 | "rollup-plugin-typescript2": "^0.27.0",
26 | "ts-node": "^8.8.2",
27 | "tslib": "^1.11.1",
28 | "typescript": "^3.8.3"
29 | },
30 | "peerDependencies": {},
31 | "scripts": {
32 | "build": "rollup -c",
33 | "build-watch": "rollup -c -w",
34 | "install-all": "npm i && cd example && npm i",
35 | "start-example": "cd example && npm start",
36 | "deploy-example": "cd example && npm run deploy",
37 | "dev": "concurrently --kill-others \"npm run build-watch\" \"npm run start-example\"",
38 | "test": "mocha -r ts-node/register ./test/**/*.spec.ts",
39 | "test:lint": "eslint ./src/**/*.ts ./src/**/*.tsx ./example/src/**/*.ts ./example/src/**/*.tsx",
40 | "test:lint:fix": "npm run test:lint -- --fix"
41 | },
42 | "files": [
43 | "dist"
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2';
2 | import pkg from './package.json';
3 |
4 | export default [
5 | {
6 | input: 'src/index.ts',
7 | external: Object.keys(pkg.peerDependencies || {}),
8 | plugins: [
9 | typescript({
10 | typescript: require('typescript'),
11 | }),
12 | ],
13 | output: [
14 | { file: pkg.main, format: 'cjs' },
15 | { file: pkg.module, format: 'esm' },
16 | {
17 | file: 'example/src/typescript-lib/index.js',
18 | format: 'es',
19 | banner: '/* eslint-disable */',
20 | },
21 | ],
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/src/createPeerConnection.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-use-before-define */
2 | const CHANNEL_LABEL = 'P2P_CHAT_CHANNEL_LABEL';
3 |
4 | export interface CreatePeerConnectionProps {
5 | remoteDescription?: string;
6 | iceServers?: RTCIceServer[];
7 | onChannelOpen: () => any;
8 | onMessageReceived: (message: string) => any;
9 | }
10 |
11 | export interface CreatePeerConnectionResponse {
12 | localDescription: string;
13 | setAnswerDescription: (answerDescription: string) => void;
14 | sendMessage: (message: string) => void;
15 | }
16 |
17 | export function createPeerConnection({
18 | remoteDescription,
19 | iceServers = [],
20 | onChannelOpen,
21 | onMessageReceived,
22 | }: CreatePeerConnectionProps): Promise {
23 | const peerConnection = new RTCPeerConnection({
24 | iceServers,
25 | });
26 | let channelInstance: RTCDataChannel;
27 |
28 | // peerConnection.oniceconnectionstatechange = () => {
29 | // if (peerConnection.iceConnectionState === 'failed' || peerConnection.iceConnectionState === 'disconnected') {
30 | // createOffer();
31 | // }
32 | // };
33 |
34 | function setupChannelAsAHost() {
35 | try {
36 | channelInstance = peerConnection.createDataChannel(CHANNEL_LABEL);
37 |
38 | channelInstance.onopen = function () {
39 | onChannelOpen();
40 | };
41 |
42 | channelInstance.onmessage = function (event) {
43 | onMessageReceived(event.data);
44 | };
45 | } catch (e) {
46 | console.error('No data channel (peerConnection)', e);
47 | }
48 | }
49 |
50 | async function createOffer() {
51 | const description = await peerConnection.createOffer();
52 | peerConnection.setLocalDescription(description);
53 | }
54 |
55 | function setupChannelAsASlave() {
56 | peerConnection.ondatachannel = function ({ channel }) {
57 | channelInstance = channel;
58 | channelInstance.onopen = function () {
59 | onChannelOpen();
60 | };
61 |
62 | channelInstance.onmessage = function (event) {
63 | onMessageReceived(event.data);
64 | };
65 | };
66 | }
67 |
68 | async function createAnswer(remoteDescription: string) {
69 | await peerConnection.setRemoteDescription(JSON.parse(remoteDescription));
70 | const description = await peerConnection.createAnswer();
71 | peerConnection.setLocalDescription(description);
72 | }
73 |
74 | function setAnswerDescription(answerDescription: string) {
75 | peerConnection.setRemoteDescription(JSON.parse(answerDescription));
76 | }
77 |
78 | function sendMessage(message: string) {
79 | if (channelInstance) {
80 | channelInstance.send(message);
81 | }
82 | }
83 |
84 | return new Promise((res) => {
85 | peerConnection.onicecandidate = function (e) {
86 | if (e.candidate === null && peerConnection.localDescription) {
87 | peerConnection.localDescription.sdp.replace('b=AS:30', 'b=AS:1638400');
88 | res({
89 | localDescription: JSON.stringify(peerConnection.localDescription),
90 | setAnswerDescription,
91 | sendMessage,
92 | });
93 | }
94 | };
95 |
96 | if (!remoteDescription) {
97 | setupChannelAsAHost();
98 | createOffer();
99 | } else {
100 | setupChannelAsASlave();
101 | createAnswer(remoteDescription);
102 | }
103 | });
104 | }
105 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { createPeerConnection, CreatePeerConnectionProps, CreatePeerConnectionResponse } from './createPeerConnection';
2 |
3 | export { createPeerConnection, CreatePeerConnectionProps, CreatePeerConnectionResponse };
4 |
--------------------------------------------------------------------------------
/test/test.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 |
3 | // No test yet...
4 | // import { createPeerConnection } from '../src/createPeerConnection';
5 |
6 | describe('test', () => {
7 | it('works but there are no real tests here yet', async () => {
8 | expect(true).to.be.equal(true);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "declarationDir": "./dist",
5 | "strict": true,
6 | "allowSyntheticDefaultImports": true,
7 | "sourceMap": true,
8 | "noUnusedParameters": true,
9 | "strictNullChecks": true,
10 | "moduleResolution": "node",
11 | "noImplicitAny": true,
12 | "outDir": "./dist",
13 | "target": "es5",
14 | "lib": ["es2018", "dom"]
15 | },
16 | "include": ["src/**/*"]
17 | }
18 |
--------------------------------------------------------------------------------