├── .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 | ![npm](https://img.shields.io/npm/v/p2p-chat) 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 | <span role="img" aria-label="smile emoji"> 37 | 😃 38 | </span> 39 | pitu-pitu 40 | <span role="img" aria-label="smile emoji"> 41 | 😃 42 | </span> 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 |
128 | Code from your buddy: 129 | 134 | Connect 135 | 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 |
119 | 124 | {!!error && {error}} 125 | Join a chat 126 | 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 | --------------------------------------------------------------------------------