From 4e038ec655d2cfb32cf1f3b8b62ce3b6613aeb2d Mon Sep 17 00:00:00 2001 From: Alan Friedman Date: Mon, 13 May 2019 09:39:17 -0400 Subject: [PATCH] Add client and server source to monorepo (#65) --- .circleci/config.yml | 36 + .gitignore | 2 - build.sh | 2 - client/.env.example | 4 + client/.gitignore | 9 + client/LICENSE | 21 + client/README.md | 3 + client/__mocks__/styles.js | 1 + client/jsconfig.json | 8 + client/package.json | 69 + client/public/favicon.ico | Bin 0 -> 7477 bytes client/public/index.html | 41 + client/public/manifest.json | 15 + client/src/actions/app.js | 20 + client/src/actions/fetch.js | 12 + client/src/actions/index.js | 4 + client/src/actions/room.js | 98 + client/src/api/config.js | 26 + client/src/api/generator.js | 13 + client/src/api/index.js | 59 + client/src/audio/beep.mp3 | Bin 0 -> 12384 bytes client/src/components/About/index.js | 198 + client/src/components/Chat/Chat.test.js | 17 + .../Chat/__snapshots__/Chat.test.js.snap | 3 + client/src/components/Chat/index.js | 294 + client/src/components/Connecting/index.js | 11 + client/src/components/FileTransfer/index.js | 119 + .../FileTransfer/styles.module.scss | 16 + client/src/components/Home/index.js | 457 + client/src/components/Home/styles.module.scss | 8 + client/src/components/Message/index.js | 36 + client/src/components/Nav/index.js | 164 + client/src/components/Notice/Notice.test.js | 14 + .../Notice/__snapshots__/Notice.test.js.snap | 15 + client/src/components/Notice/index.js | 26 + .../src/components/Notice/styles.module.css | 10 + client/src/components/RoomLink/index.js | 65 + client/src/components/RoomLocked/index.js | 11 + client/src/components/Settings/index.js | 64 + client/src/components/Username/index.js | 19 + client/src/components/Welcome/index.js | 45 + client/src/config/env.js | 1 + client/src/containers/Chat/index.js | 15 + client/src/containers/Home/index.js | 66 + client/src/img/logo-dark.png | Bin 0 -> 7477 bytes client/src/img/logo.png | Bin 0 -> 7587 bytes client/src/img/menu.png | Bin 0 -> 598 bytes client/src/index.css | 14 + client/src/index.html | 15 + client/src/index.js | 12 + client/src/logo.svg | 7 + client/src/reducers/activities.js | 242 + client/src/reducers/app.js | 59 + client/src/reducers/index.js | 16 + client/src/reducers/room.js | 142 + client/src/reducers/user.js | 25 + client/src/root.js | 37 + client/src/serviceWorker.js | 135 + client/src/setupTests.js | 4 + client/src/store/index.js | 27 + client/src/stylesheets/app.sass | 228 + client/src/stylesheets/colors.sass | 10 + client/src/stylesheets/footer.sass | 0 client/src/stylesheets/nav.sass | 104 + client/src/stylesheets/postcss.config.js | 5 + client/src/stylesheets/typography.sass | 19 + client/src/test/setup.js | 4 + client/src/utils/ImageZoom.js | 250 + client/src/utils/crypto.js | 169 + client/src/utils/dom.js | 14 + client/src/utils/file.js | 29 + client/src/utils/index.js | 4 + client/src/utils/message.js | 122 + client/src/utils/socket.js | 17 + client/yarn.lock | 11228 ++++++++++++++++ package.json | 2 +- readme.md | 17 +- server/.babelrc | 7 + server/.env.sample | 7 + server/.eslintrc | 7 + server/.gitignore | 7 + server/LICENSE | 21 + server/README.md | 3 + server/build.sh | 9 + server/package.json | 50 + server/src/config.json | 4 + server/src/index.js | 143 + server/src/socket.js | 134 + server/src/utils/index.js | 3 + server/src/utils/mailer.js | 24 + server/src/utils/utils.test.js | 5 + server/yarn.lock | 5988 ++++++++ setup.sh | 3 - 93 files changed, 21467 insertions(+), 22 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 client/.env.example create mode 100644 client/.gitignore create mode 100644 client/LICENSE create mode 100644 client/README.md create mode 100644 client/__mocks__/styles.js create mode 100644 client/jsconfig.json create mode 100644 client/package.json create mode 100644 client/public/favicon.ico create mode 100644 client/public/index.html create mode 100644 client/public/manifest.json create mode 100644 client/src/actions/app.js create mode 100644 client/src/actions/fetch.js create mode 100644 client/src/actions/index.js create mode 100644 client/src/actions/room.js create mode 100644 client/src/api/config.js create mode 100644 client/src/api/generator.js create mode 100644 client/src/api/index.js create mode 100644 client/src/audio/beep.mp3 create mode 100644 client/src/components/About/index.js create mode 100644 client/src/components/Chat/Chat.test.js create mode 100644 client/src/components/Chat/__snapshots__/Chat.test.js.snap create mode 100644 client/src/components/Chat/index.js create mode 100644 client/src/components/Connecting/index.js create mode 100644 client/src/components/FileTransfer/index.js create mode 100644 client/src/components/FileTransfer/styles.module.scss create mode 100644 client/src/components/Home/index.js create mode 100644 client/src/components/Home/styles.module.scss create mode 100644 client/src/components/Message/index.js create mode 100644 client/src/components/Nav/index.js create mode 100644 client/src/components/Notice/Notice.test.js create mode 100644 client/src/components/Notice/__snapshots__/Notice.test.js.snap create mode 100644 client/src/components/Notice/index.js create mode 100644 client/src/components/Notice/styles.module.css create mode 100644 client/src/components/RoomLink/index.js create mode 100644 client/src/components/RoomLocked/index.js create mode 100644 client/src/components/Settings/index.js create mode 100644 client/src/components/Username/index.js create mode 100644 client/src/components/Welcome/index.js create mode 100644 client/src/config/env.js create mode 100644 client/src/containers/Chat/index.js create mode 100644 client/src/containers/Home/index.js create mode 100644 client/src/img/logo-dark.png create mode 100644 client/src/img/logo.png create mode 100644 client/src/img/menu.png create mode 100644 client/src/index.css create mode 100644 client/src/index.html create mode 100644 client/src/index.js create mode 100644 client/src/logo.svg create mode 100644 client/src/reducers/activities.js create mode 100644 client/src/reducers/app.js create mode 100644 client/src/reducers/index.js create mode 100644 client/src/reducers/room.js create mode 100644 client/src/reducers/user.js create mode 100644 client/src/root.js create mode 100644 client/src/serviceWorker.js create mode 100644 client/src/setupTests.js create mode 100644 client/src/store/index.js create mode 100644 client/src/stylesheets/app.sass create mode 100644 client/src/stylesheets/colors.sass create mode 100644 client/src/stylesheets/footer.sass create mode 100644 client/src/stylesheets/nav.sass create mode 100644 client/src/stylesheets/postcss.config.js create mode 100644 client/src/stylesheets/typography.sass create mode 100644 client/src/test/setup.js create mode 100644 client/src/utils/ImageZoom.js create mode 100644 client/src/utils/crypto.js create mode 100644 client/src/utils/dom.js create mode 100644 client/src/utils/file.js create mode 100644 client/src/utils/index.js create mode 100644 client/src/utils/message.js create mode 100644 client/src/utils/socket.js create mode 100644 client/yarn.lock create mode 100644 server/.babelrc create mode 100644 server/.env.sample create mode 100644 server/.eslintrc create mode 100644 server/.gitignore create mode 100644 server/LICENSE create mode 100644 server/README.md create mode 100755 server/build.sh create mode 100644 server/package.json create mode 100644 server/src/config.json create mode 100644 server/src/index.js create mode 100644 server/src/socket.js create mode 100644 server/src/utils/index.js create mode 100644 server/src/utils/mailer.js create mode 100644 server/src/utils/utils.test.js create mode 100644 server/yarn.lock delete mode 100755 setup.sh diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..ff23b07 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,36 @@ +# Javascript Node CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-javascript/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + - image: circleci/node:8.10 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/mongo:3.4.4 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "yarn.lock" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: yarn install + + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum "yarn.lock" }} + + - run: yarn test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 15dfd69..0144959 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ .DS_Store -server -client node_modules *.log \ No newline at end of file diff --git a/build.sh b/build.sh index 7588baf..30f3b79 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,3 @@ -yarn setup - echo "building client..." cd client yarn --production=false diff --git a/client/.env.example b/client/.env.example new file mode 100644 index 0000000..2695add --- /dev/null +++ b/client/.env.example @@ -0,0 +1,4 @@ +REACT_APP_API_HOST=localhost +REACT_APP_API_PROTOCOL=http +REACT_APP_API_PORT=3001 +REACT_APP_COMMIT_SHA=some_sha \ No newline at end of file diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..e2178dd --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,9 @@ +node_modules +.DS_Store +dist +coverage +*.log +.env* +!.env.example +build/ +*sublime* \ No newline at end of file diff --git a/client/LICENSE b/client/LICENSE new file mode 100644 index 0000000..94da581 --- /dev/null +++ b/client/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-present darkwire.io + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..076caff --- /dev/null +++ b/client/README.md @@ -0,0 +1,3 @@ +# Darkwire Client + +This is the client for [Darkwire](https://github.com/darkwire/darkwire.io). It requires [darkwire-server](../server) in order to run. \ No newline at end of file diff --git a/client/__mocks__/styles.js b/client/__mocks__/styles.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/client/__mocks__/styles.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/client/jsconfig.json b/client/jsconfig.json new file mode 100644 index 0000000..e9dfe87 --- /dev/null +++ b/client/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "paths": { + "@/*":["src/*"] + } + } +} \ No newline at end of file diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..4d73eb2 --- /dev/null +++ b/client/package.json @@ -0,0 +1,69 @@ +{ + "name": "darkwire-client", + "version": "2.0.0-beta.12", + "main": "index.js", + "contributors": [ + { + "name": "Daniel Seripap" + }, + { + "name": "Alan Friedman" + } + ], + "license": "MIT", + "dependencies": { + "autosize": "^4.0.2", + "bootstrap": "^4.3.1", + "clipboard": "^2.0.4", + "feather-icons": "^4.21.0", + "jquery": "^3.4.1", + "lodash": "^4.17.11", + "moment": "^2.24.0", + "node-sass": "^4.12.0", + "popper.js": "^1.15.0", + "query-string": "^6.5.0", + "randomcolor": "^0.5.4", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "react-feather": "^1.1.6", + "react-linkify": "^0.2.2", + "react-modal": "^3.8.1", + "react-redux": "^7.0.3", + "react-router-dom": "^5.0.0", + "react-scripts": "3.0.0", + "react-simple-dropdown": "^3.2.3", + "redux": "^4.0.1", + "redux-thunk": "^2.3.0", + "sanitize-html": "^1.20.1", + "shortid": "^2.2.14", + "socket.io-client": "^2.2.0", + "tinycon": "^0.6.8", + "webcrypto-shim": "^0.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "REACT_APP_COMMIT_SHA=`git rev-parse --short HEAD` react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.12.1", + "enzyme-to-json": "^3.3.5" + } +} diff --git a/client/public/favicon.ico b/client/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..359faae3a55d5a36941f27506441e5bdf3c891bc GIT binary patch literal 7477 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 NP?A@dD}Q6=|9_goPZ + + + + + + + + + + + + + darkwire.io - instant encrypted web chat + + + +
+ + + diff --git a/client/public/manifest.json b/client/public/manifest.json new file mode 100644 index 0000000..2553b79 --- /dev/null +++ b/client/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Darkwire", + "name": "Darkwire.io - encrypted web chat", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/client/src/actions/app.js b/client/src/actions/app.js new file mode 100644 index 0000000..42e37f5 --- /dev/null +++ b/client/src/actions/app.js @@ -0,0 +1,20 @@ +export const openModal = payload => ({ type: 'OPEN_MODAL', payload }) +export const closeModal = () => ({ type: 'CLOSE_MODAL' }) + +export const setScrolledToBottom = payload => ({ type: 'SET_SCROLLED_TO_BOTTOM', payload }) + +export const showNotice = payload => async (dispatch) => { + dispatch({ type: 'SHOW_NOTICE', payload }) +} + +export const toggleWindowFocus = payload => async (dispatch) => { + dispatch({ type: 'TOGGLE_WINDOW_FOCUS', payload }) +} + +export const toggleSoundEnabled = payload => async (dispatch) => { + dispatch({ type: 'TOGGLE_SOUND_ENABLED', payload }) +} + +export const toggleSocketConnected = payload => async (dispatch) => { + dispatch({ type: 'TOGGLE_SOCKET_CONNECTED', payload }) +} diff --git a/client/src/actions/fetch.js b/client/src/actions/fetch.js new file mode 100644 index 0000000..cfa95c8 --- /dev/null +++ b/client/src/actions/fetch.js @@ -0,0 +1,12 @@ +const methodMap = { + GET: '', + POST: 'CREATE_', + PUT: 'UPDATE_', + DELETE: 'DELETE_', +} + +export const fetchStart = (name, method, resourceId, meta) => ({ type: `FETCH_${methodMap[method]}${name.toUpperCase()}_START`, payload: { resourceId, meta } }) + +export const fetchSuccess = (name, method, response) => ({ type: `FETCH_${methodMap[method]}${name.toUpperCase()}_SUCCESS`, payload: response }) + +export const fetchFailure = (name, method, response) => ({ type: `FETCH_${methodMap[method]}${name.toUpperCase()}_FAILURE`, payload: response }) diff --git a/client/src/actions/index.js b/client/src/actions/index.js new file mode 100644 index 0000000..1297389 --- /dev/null +++ b/client/src/actions/index.js @@ -0,0 +1,4 @@ +export * from './fetch' +export * from './room' +export * from './app' + diff --git a/client/src/actions/room.js b/client/src/actions/room.js new file mode 100644 index 0000000..37b9eff --- /dev/null +++ b/client/src/actions/room.js @@ -0,0 +1,98 @@ +import fetch from 'api' +import isEqual from 'lodash/isEqual' +import { + process as processMessage, + prepare as prepareMessage, +} from 'utils/message' +import { getIO } from 'utils/socket' + +export const createRoom = id => async dispatch => fetch({ + resourceName: 'handshake', + method: 'POST', + body: { + roomId: id, + }, +}, dispatch, 'handshake') + +export const receiveSocketMessage = payload => async (dispatch, getState) => { + const state = getState() + const message = await processMessage(payload, state) + // Pass current state to all HANDLE_SOCKET_MESSAGE reducers for convenience, since each may have different needs + dispatch({ type: `HANDLE_SOCKET_MESSAGE_${message.type}`, payload: { payload: message.payload, state } }) +} + +export const createUser = payload => async (dispatch) => { + dispatch({ type: 'CREATE_USER', payload }) +} + +export const sendUserEnter = payload => async () => { + getIO().emit('USER_ENTER', { + publicKey: payload.publicKey, + }) +} + +export const receiveUserExit = payload => async (dispatch, getState) => { + const state = getState() + const exitingUser = state.room.members.find(m => !payload.map(p => JSON.stringify(p.publicKey)).includes(JSON.stringify(m.publicKey))) + const exitingUserId = exitingUser.id + const exitingUsername = exitingUser.username + + dispatch({ + type: 'USER_EXIT', + payload: { + members: payload, + id: exitingUserId, + username: exitingUsername, + }, + }) +} + +export const receiveUserEnter = payload => async (dispatch) => { + dispatch({ type: 'USER_ENTER', payload }) +} + +export const onFileTransfer = payload => async (dispatch) => { + dispatch({ type: 'PREFLIGHT_FILE_TRANSFER', payload }) +} + +export const sendSocketMessage = payload => async (dispatch, getState) => { + const state = getState() + const msg = await prepareMessage(payload, state) + dispatch({ type: `SEND_SOCKET_MESSAGE_${msg.original.type}`, payload: msg.original.payload }) + getIO().emit('PAYLOAD', msg.toSend) +} + +export const toggleLockRoom = () => async (dispatch, getState) => { + const state = getState() + getIO().emit('TOGGLE_LOCK_ROOM', null, (res) => { + dispatch({ + type: 'TOGGLE_LOCK_ROOM', + payload: { + locked: res.isLocked, + username: state.user.username, + sender: state.user.id, + }, + }) + }) +} + +export const receiveToggleLockRoom = payload => async (dispatch, getState) => { + const state = getState() + + const lockedByUser = state.room.members.find(m => isEqual(m.publicKey, payload.publicKey)) + const lockedByUsername = lockedByUser.username + const lockedByUserId = lockedByUser.id + + dispatch({ + type: 'RECEIVE_TOGGLE_LOCK_ROOM', + payload: { + username: lockedByUsername, + locked: payload.locked, + id: lockedByUserId, + }, + }) +} + +export const clearActivities = () => async (dispatch) => { + dispatch({ type: 'CLEAR_ACTIVITIES' }) +} diff --git a/client/src/api/config.js b/client/src/api/config.js new file mode 100644 index 0000000..30e1c50 --- /dev/null +++ b/client/src/api/config.js @@ -0,0 +1,26 @@ +let host +let protocol +let port + +switch (process.env.NODE_ENV) { + case 'staging': + host = process.env.REACT_APP_API_HOST + protocol = process.env.REACT_APP_API_PROTOCOL || 'https' + port = process.env.REACT_APP_API_PORT || 443 + break + case 'production': + host = process.env.REACT_APP_API_HOST + protocol = process.env.REACT_APP_API_PROTOCOL || 'https' + port = process.env.REACT_APP_API_PORT || 443 + break + default: + host = process.env.REACT_APP_API_HOST || 'localhost' + protocol = process.env.REACT_APP_API_PROTOCOL || 'http' + port = process.env.REACT_APP_API_PORT || 3001 +} + +export default { + host, + port, + protocol, +} diff --git a/client/src/api/generator.js b/client/src/api/generator.js new file mode 100644 index 0000000..9dd44f1 --- /dev/null +++ b/client/src/api/generator.js @@ -0,0 +1,13 @@ +import config from './config' + +export default (resourceName = '') => { + const { port, protocol, host } = config + + const resourcePath = resourceName + + if (!host) { + return `/${resourcePath}`; + } + + return `${protocol}://${host}:${port}/${resourcePath}` +} diff --git a/client/src/api/index.js b/client/src/api/index.js new file mode 100644 index 0000000..b4f627e --- /dev/null +++ b/client/src/api/index.js @@ -0,0 +1,59 @@ +import { + fetchStart, + fetchSuccess, + fetchFailure, +} from 'actions' +import queryString from 'querystring' +import generateUrl from './generator' + +export default (opts, dispatch, name, metaOpts = {}) => { + const method = opts.method || 'GET' + const resourceId = opts.resourceId + let url = generateUrl(opts.resourceName, resourceId) + + const config = { + method, + headers: {}, + type: 'cors', + } + + if (opts.body) { + config.body = JSON.stringify(opts.body) + config.headers['Content-Type'] = 'application/json' + } + + if (opts.query) { + url = `${url}?${queryString.stringify(opts.query)}` + } + + return new Promise((resolve, reject) => { + const meta = { ...metaOpts, timestamp: Date.now() } + dispatch(fetchStart(name, method, resourceId, meta)) + return window.fetch(url, config) + .then(async (response) => { + let json = {} + + try { + json = await response.json() + } catch (e) { + throw new Error(e) + } + + const dispatchOps = { + response, + json, + resourceId, + meta, + } + + if (response.ok) { + dispatch(fetchSuccess(name, method, dispatchOps)) + return resolve(dispatchOps) + } + + dispatch(fetchFailure(name, method, dispatchOps)) + + return reject(dispatchOps) + }) + }) +} diff --git a/client/src/audio/beep.mp3 b/client/src/audio/beep.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..738c24f824c8154c6f64f671e694e487d5db1df6 GIT binary patch literal 12384 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 Yf9n5_U-zH-fBm}udq2!$|EHh-0 +

Version

+

Client + Commit SHA: {process.env.REACT_APP_COMMIT_SHA}

+

Server + Commit SHA: {this.props.serverSHA}

+
+ +

Software

+

This software uses the Web Cryptography API to + encrypt data which is transferred using secure WebSockets. + Messages are never stored on a server or sent over the wire in plain-text.

+

We believe in privacy and transparency. +  View the source code and documentation on GitHub.

+
+ +

Report Abuse

+

To report any content that violates our Acceptable Use Policy below, email us at abuse[at]darkwire.io or submit the room ID below to report anonymously.

+
+ {this.state.abuseReported &&
Thank you!
} +
+ + +
+
+
+ +

Acceptable Use Policy

+ +

This Acceptable Use Policy (this “Policy”) describes prohibited uses of the web services offered by Darkwire and its affiliates (the “Services”) and the website located at https://darkwire.io (the “Darkwire Site”). The examples described in this Policy are not exhaustive. We may modify this Policy at any time by posting a revised version on the Darkwire Site. By using the Services or accessing the Darkwire Site, you agree to the latest version of this Policy. If you violate the Policy or authorize or help others to do so, we may suspend or terminate your use of the Services.

+ + No Illegal, Harmful, or Offensive Use or Content +

You may not use, or encourage, promote, facilitate or instruct others to use, the Services or Darkwire Site for any illegal, harmful, fraudulent, infringing or offensive use, or to transmit, store, display, distribute or otherwise make available content that is illegal, harmful, fraudulent, infringing or offensive. Prohibited activities or content include:

+ +
    +
  • Illegal, Harmful or Fraudulent Activities. Any activities that are illegal, that violate the rights of others, or that may be harmful to others, our operations or reputation, including disseminating, promoting or facilitating child pornography, offering or disseminating fraudulent goods, services, schemes, or promotions, make-money-fast schemes, ponzi and pyramid schemes, phishing, or pharming.
  • + +
  • Infringing Content. Content that infringes or misappropriates the intellectual property or proprietary rights of others.
  • + +
  • Offensive Content. Content that is defamatory, obscene, abusive, invasive of privacy, or otherwise objectionable, including content that constitutes child pornography, relates to bestiality, or depicts non-consensual sex acts.
  • + +
  • Harmful Content. Content or other computer technology that may damage, interfere with, surreptitiously intercept, or expropriate any system, program, or data, including viruses, Trojan horses, worms, time bombs, or cancelbots.
  • +
+ + No Security Violations +
You may not use the Services to violate the security or integrity of any network, computer or communications system, software application, or network or computing device (each, a “System”). Prohibited activities include: + +
    +
  • Unauthorized Access. Accessing or using any System without permission, including attempting to probe, scan, or test the vulnerability of a System or to breach any security or authentication measures used by a System.
  • + +
  • Interception. Monitoring of data or traffic on a System without permission.
  • + +
  • Falsification of Origin. Forging TCP-IP packet headers, e-mail headers, or any part of a message describing its origin or route. The legitimate use of aliases and anonymous remailers is not prohibited by this provision.
  • +
+ + No Network Abuse +
You may not make network connections to any users, hosts, or networks unless you have permission to communicate with them. Prohibited activities include: + +
    +
  • Monitoring or Crawling. Monitoring or crawling of a System that impairs or disrupts the System being monitored or crawled.
  • + +
  • Denial of Service (DoS). Inundating a target with communications requests so the target either cannot respond to legitimate traffic or responds so slowly that it becomes ineffective.
  • + +
  • Intentional Interference. Interfering with the proper functioning of any System, including any deliberate attempt to overload a system by mail bombing, news bombing, broadcast attacks, or flooding techniques.
  • + +
  • Operation of Certain Network Services. Operating network services like open proxies, open mail relays, or open recursive domain name servers.
  • + +
  • Avoiding System Restrictions. Using manual or electronic means to avoid any use limitations placed on a System, such as access and storage restrictions.
  • +
+ + No E-Mail or Other Message Abuse +
You will not distribute, publish, send, or facilitate the sending of unsolicited mass e-mail or other messages, promotions, advertising, or solicitations (like “spam”), including commercial advertising and informational announcements. You will not alter or obscure mail headers or assume a sender’s identity without the sender’s explicit permission. You will not collect replies to messages sent from another internet service provider if those messages violate this Policy or the acceptable use policy of that provider. + + Our Monitoring and Enforcement +
We reserve the right, but do not assume the obligation, to investigate any violation of this Policy or misuse of the Services or Darkwire Site. We may: +
    +
  • investigate violations of this Policy or misuse of the Services or Darkwire Site; or
  • +
  • remove, disable access to, or modify any content or resource that violates this Policy or any other agreement we have with you for use of the Services or the Darkwire Site.
  • +
  • We may report any activity that we suspect violates any law or regulation to appropriate law enforcement officials, regulators, or other appropriate third parties. Our reporting may include disclosing appropriate customer information. We also may cooperate with appropriate law enforcement agencies, regulators, or other appropriate third parties to help with the investigation and prosecution of illegal conduct by providing network and systems information related to alleged violations of this Policy.
  • +
+ + Reporting of Violations of this Policy +
If you become aware of any violation of this Policy, you will immediately notify us and provide us with assistance, as requested, to stop or remedy the violation. To report any violation of this Policy, please follow our abuse reporting process. +
+
+ +

Disclaimer

+

WARNING: Darkwire does not mask IP addresses nor can verify the integrity of parties recieving messages. +  Proceed with caution and always confirm recipients beforre starting a chat session.

+

Please also note that ALL CHATROOMS are public. +  Anyone can guess your room URL. If you need a more-private room, use the lock feature or set the URL manually by entering a room ID after "darkwire.io/". +

+
+ +

Terms of Service ("Terms")

+

Last updated: December 11, 2017

+

Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the https://darkwire.io website (the "Service") operated by Darkwire ("us", "we", or "our").

+

Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. These Terms apply to all visitors, users and others who access or use the Service.

+

By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service.

+ Links To Other Web Sites +

Our Service may contain links to third-party web sites or services that are not owned or controlled by Darkwire.

+

Darkwire has no control over, and assumes no responsibility for, the content, privacy policies, or practices of any third party web sites or services. You further acknowledge and agree that Darkwire shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such web sites or services.

+

We strongly advise you to read the terms and conditions and privacy policies of any third-party web sites or services that you visit.

+ Termination +

We may terminate or suspend access to our Service immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.

+

All provisions of the Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, + warranty disclaimers, indemnity and limitations of liability.

+ Governing Law +

These Terms shall be governed and construed in accordance with the laws of New York, United States, without regard to its conflict of law provisions.

+

Our failure to enforce any right or provision of these Terms will not be considered a waiver of those rights. If any provision of these Terms is held to be +invalid or unenforceable by a court, the remaining provisions of these Terms will remain in effect. These Terms constitute the entire agreement between us +regarding our Service, and supersede and replace any prior agreements we might have between us regarding the Service.

+ No Warranties; Exclusion of Liability; Indemnification +

OUR WEBSITE IS OPERATED BY Darkwire ON AN "AS IS," "AS AVAILABLE" BASIS, WITHOUT REPRESENTATIONS OR WARRANTIES OF ANY KIND. TO THE FULLEST EXTENT PERMITTED BY LAW, Darkwire SPECIFICALLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, INCLUDING ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NONINFRINGEMENT FOR OUR WEBSITE AND ANY CONTRACTS AND SERVICES YOU PURCHASE THROUGH IT. Darkwire SHALL NOT HAVE ANY LIABILITY OR RESPONSIBILITY FOR ANY ERRORS OR OMISSIONS IN THE CONTENT OF OUR WEBSITE, FOR CONTRACTS OR SERVICES SOLD THROUGH OUR WEBSITE, FOR YOUR ACTION OR INACTION IN CONNECTION WITH OUR WEBSITE OR FOR ANY DAMAGE TO YOUR COMPUTER OR DATA OR ANY OTHER DAMAGE YOU MAY INCUR IN CONNECTION WITH OUR WEBSITE. YOUR USE OF OUR WEBSITE AND ANY CONTRACTS OR SERVICES ARE AT YOUR OWN RISK. IN NO EVENT SHALL EITHER Darkwire OR THEIR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OF OUR WEBSITE, CONTRACTS AND SERVICES PURCHASED THROUGH OUR WEBSITE, THE DELAY OR INABILITY TO USE OUR WEBSITE OR OTHERWISE ARISING IN CONNECTION WITH OUR WEBSITE, CONTRACTS OR RELATED SERVICES, WHETHER BASED ON CONTRACT, +TORT, STRICT LIABILITY OR OTHERWISE, EVEN IF ADVISED OF THE POSSIBILITY OF ANY SUCH DAMAGES. IN NO EVENT SHALL Darkwire’s LIABILITY FOR ANY DAMAGE CLAIM EXCEED THE AMOUNT PAID BY YOU TO Darkwire FOR THE TRANSACTION GIVING RISE TO SUCH DAMAGE CLAIM.

+

SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO YOU.

+

WITHOUT LIMITING THE FOREGOING, Darkwire DO NOT REPRESENT OR WARRANT THAT THE INFORMATION ON THE WEBITE IS ACCURATE, COMPLETE, RELIABLE, USEFUL, TIMELY OR CURRENT OR THAT OUR WEBSITE WILL OPERATE WITHOUT INTERRUPTION OR ERROR.

+

YOU AGREE THAT ALL TIMES, YOU WILL LOOK TO ATTORNEYS FROM WHOM YOU PURCHASE SERVICES FOR ANY CLAIMS OF ANY NATURE, INCLUDING LOSS, DAMAGE, OR WARRANTY. Darkwire AND THEIR RESPECTIVE AFFILIATES MAKE NO REPRESENTATION OR GUARANTEES ABOUT ANY CONTRACTS AND SERVICES OFFERED THROUGH OUR WEBSITE.

+

Darkwire MAKES NO REPRESENTATION THAT CONTENT PROVIDED ON OUR WEBSITE, CONTRACTS, OR RELATED SERVICES ARE APPLICABLE OR APPROPRIATE FOR USE IN ALL +JURISDICTIONS.

+ Indemnification +

You agree to defend, indemnify and hold Darkwire harmless from and against any and all claims, damages, costs and expenses, including attorneys' fees, arising +from or related to your use of our Website or any Contracts or Services you purchase through it.

+ Changes +

We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will try to provide at least 30 days notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.

+

By continuing to access or use our Service after those revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new +terms, please stop using the Service.

+ Contact Us +

If you have any questions about these Terms, please contact us at hello[at]darkwire.io.

+
+ +

Contact

+

Questions/comments? Email us at hello[at]darkwire.io

+

Found a bug or want a new feature? Open a ticket on Github.

+
+ +

Donate

+

Darkwire is maintained and hosted by two developers with full-time jobs. If you get some value + from this service we would appreciate any donation you can afford. We use these funds for + server and DNS costs. Thank you! +

+ Bitcoin +

189sPnHGcjP5uteg2UuNgcJ5eoaRAP4Bw4

+ Ethereum +

0xD6e3D881036903999E2c0480fe9d2c20600C1c28

+ Litecoin +

LUViQeSggBBtYoN2qNtXSuxYoRMzRY8CSX

+ PayPal: +
+
+ + + + +
+ + ) + } +} + +About.propTypes = { + serverSHA: PropTypes.string.isRequired, + roomId: PropTypes.string.isRequired, +} + +export default About diff --git a/client/src/components/Chat/Chat.test.js b/client/src/components/Chat/Chat.test.js new file mode 100644 index 0000000..a33de83 --- /dev/null +++ b/client/src/components/Chat/Chat.test.js @@ -0,0 +1,17 @@ +import React from 'react' +import { mount } from 'enzyme' +import toJson from 'enzyme-to-json' +import { Chat } from './index.js' + +const sendSocketMessage = jest.fn() + +test('Chat Component', () => { + const component = mount( + {}} focusChat={false} userId="foo" username="user" showNotice={() => {}} clearActivities={() => {}} sendSocketMessage={sendSocketMessage} /> + ) + + const componentJSON = toJson(component) + + expect(component).toMatchSnapshot() + expect(componentJSON.children.length).toBe(1) +}) diff --git a/client/src/components/Chat/__snapshots__/Chat.test.js.snap b/client/src/components/Chat/__snapshots__/Chat.test.js.snap new file mode 100644 index 0000000..0627a01 --- /dev/null +++ b/client/src/components/Chat/__snapshots__/Chat.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Chat Component 1`] = `ReactWrapper {}`; diff --git a/client/src/components/Chat/index.js b/client/src/components/Chat/index.js new file mode 100644 index 0000000..f2ba71d --- /dev/null +++ b/client/src/components/Chat/index.js @@ -0,0 +1,294 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import sanitizeHtml from 'sanitize-html' +import FileTransfer from 'components/FileTransfer' +import { CornerDownRight } from 'react-feather' +import { connect } from 'react-redux' +import { clearActivities, showNotice } from '../../actions' +import { getSelectedText, hasTouchSupport } from '../../utils/dom' +// Disable for now +// import autosize from 'autosize' + +export class Chat extends Component { + constructor(props) { + super(props) + this.state = { + message: '', + touchSupport: hasTouchSupport, + shiftKeyDown: false, + } + + this.commands = [{ + command: 'nick', + description: 'Changes nickname.', + paramaters: ['{username}'], + usage: '/nick {username}', + scope: 'global', + action: (params) => { // eslint-disable-line + let newUsername = params.join(' ') || '' // eslint-disable-line + + // Remove things that arent digits or chars + newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-') + + const errors = [] + + if (!newUsername.trim().length) { + errors.push('Username cannot be blank') + } + + if (newUsername.toString().length > 16) { + errors.push('Username cannot be greater than 16 characters') + } + + if (!newUsername.match(/^[A-Z]/i)) { + errors.push('Username must start with a letter') + } + + if (errors.length) { + return this.props.showNotice({ + message: `${errors.join(', ')}`, + level: 'error', + }) + } + + this.props.sendSocketMessage({ + type: 'CHANGE_USERNAME', + payload: { + id: this.props.userId, + newUsername, + currentUsername: this.props.username, + }, + }) + }, + }, { + command: 'help', + description: 'Shows a list of commands.', + paramaters: [], + usage: '/help', + scope: 'local', + action: (params) => { // eslint-disable-line + const validCommands = this.commands.map(command => `/${command.command}`) + this.props.showNotice({ + message: `Valid commands: ${validCommands.sort().join(', ')}`, + level: 'info', + }) + }, + }, { + command: 'me', + description: 'Invoke virtual action', + paramaters: ['{action}'], + usage: '/me {action}', + scope: 'global', + action: (params) => { // eslint-disable-line + const actionMessage = params.join(' ') + if (!actionMessage.trim().length) { + return false + } + + this.props.sendSocketMessage({ + type: 'USER_ACTION', + payload: { + action: actionMessage, + }, + }) + }, + }, { + command: 'clear', + description: 'Clears the chat screen', + paramaters: [], + usage: '/clear', + scope: 'local', + action: (params = null) => { // eslint-disable-line + this.props.clearActivities() + }, + }] + } + + componentDidMount() { + if (!hasTouchSupport) { + // Disable for now due to vary issues: + // Paste not working, shift+enter line breaks + // autosize(this.textInput); + this.textInput.addEventListener('autosize:resized', () => { + this.props.scrollToBottom() + }) + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.focusChat) { + if (!getSelectedText()) { + // Don't focus for now, evaulate UX benfits + // this.textInput.focus() + } + } + } + + componentDidUpdate(nextProps, nextState) { + if (!nextState.message.trim().length) { + // autosize.update(this.textInput) + } + } + + handleKeyUp(e) { + if (e.key === 'Shift') { + this.setState({ + shiftKeyDown: false, + }) + } + } + + handleKeyPress(e) { + if (e.key === 'Shift') { + this.setState({ + shiftKeyDown: true, + }) + } + // Fix when autosize is enabled - line breaks require shift+enter twice + if (e.key === 'Enter' && !hasTouchSupport && !this.state.shiftKeyDown) { + e.preventDefault() + if (this.canSend()) { + this.sendMessage() + } else { + this.setState({ + message: '', + }) + } + } + } + + executeCommand(command) { + const commandToExecute = this.commands.find(cmnd => cmnd.command === command.command) + + if (commandToExecute) { + const { params } = command + const commandResult = commandToExecute.action(params) + + return commandResult + } + + return null + } + + handleSendClick() { + this.sendMessage.bind(this) + this.textInput.focus() + } + + handleFormSubmit(evt) { + evt.preventDefault() + this.sendMessage() + } + + parseCommand(message) { + const commandTrigger = { + command: null, + params: [], + } + + if (message.charAt(0) === '/') { + const parsedCommand = message.replace('/', '').split(' ') + commandTrigger.command = sanitizeHtml(parsedCommand[0]) || null + // Get params + if (parsedCommand.length >= 2) { + for (let i = 1; i < parsedCommand.length; i++) { + commandTrigger.params.push(parsedCommand[i]) + } + } + + return commandTrigger + } + + return false + } + + sendMessage() { + if (!this.canSend()) { + return + } + + const { message } = this.state + const isCommand = this.parseCommand(message) + + if (isCommand) { + const res = this.executeCommand(isCommand) + if (res === false) { + return + } + } else { + this.props.sendSocketMessage({ + type: 'SEND_MESSAGE', + payload: { + text: message, + timestamp: Date.now(), + }, + }) + } + + this.setState({ + message: '', + }) + } + + handleInputChange(evt) { + this.setState({ + message: evt.target.value, + }) + } + + canSend() { + return this.state.message.trim().length + } + + render() { + const touchSupport = this.state.touchSupport + + return ( +
+