mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 10:49:02 +00:00
Apply prettier on client
This commit is contained in:
parent
3f17ca707b
commit
e06b0e384c
@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*":["src/*"]
|
"@/*": ["src/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class='h-100'>
|
<html lang="en" class="h-100">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
@ -20,12 +20,12 @@
|
|||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<meta name="robots" content="index,nofollow">
|
<meta name="robots" content="index,nofollow" />
|
||||||
<meta name="googlebot" content="index,nofollow">
|
<meta name="googlebot" content="index,nofollow" />
|
||||||
<meta name="description" content="darkwire.io is the simplest way to chat with encryption online.">
|
<meta name="description" content="darkwire.io is the simplest way to chat with encryption online." />
|
||||||
<title>darkwire.io - instant encrypted web chat</title>
|
<title>darkwire.io - instant encrypted web chat</title>
|
||||||
</head>
|
</head>
|
||||||
<body class='h-100'>
|
<body class="h-100">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root" class="h-100"></div>
|
<div id="root" class="h-100"></div>
|
||||||
<!--
|
<!--
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
export const openModal = payload => ({ type: 'OPEN_MODAL', payload })
|
export const openModal = payload => ({ type: 'OPEN_MODAL', payload });
|
||||||
export const closeModal = () => ({ type: 'CLOSE_MODAL' })
|
export const closeModal = () => ({ type: 'CLOSE_MODAL' });
|
||||||
|
|
||||||
export const setScrolledToBottom = payload => ({ type: 'SET_SCROLLED_TO_BOTTOM', payload })
|
export const setScrolledToBottom = payload => ({ type: 'SET_SCROLLED_TO_BOTTOM', payload });
|
||||||
|
|
||||||
export const showNotice = payload => async (dispatch) => {
|
export const showNotice = payload => async dispatch => {
|
||||||
dispatch({ type: 'SHOW_NOTICE', payload })
|
dispatch({ type: 'SHOW_NOTICE', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const toggleWindowFocus = payload => async (dispatch) => {
|
export const toggleWindowFocus = payload => async dispatch => {
|
||||||
dispatch({ type: 'TOGGLE_WINDOW_FOCUS', payload })
|
dispatch({ type: 'TOGGLE_WINDOW_FOCUS', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const toggleSoundEnabled = payload => async (dispatch) => {
|
export const toggleSoundEnabled = payload => async dispatch => {
|
||||||
dispatch({ type: 'TOGGLE_SOUND_ENABLED', payload })
|
dispatch({ type: 'TOGGLE_SOUND_ENABLED', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const toggleNotificationEnabled = payload => async (dispatch) => {
|
export const toggleNotificationEnabled = payload => async dispatch => {
|
||||||
dispatch({ type: 'TOGGLE_NOTIFICATION_ENABLED', payload })
|
dispatch({ type: 'TOGGLE_NOTIFICATION_ENABLED', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const toggleNotificationAllowed = payload => async (dispatch) => {
|
export const toggleNotificationAllowed = payload => async dispatch => {
|
||||||
dispatch({ type: 'TOGGLE_NOTIFICATION_ALLOWED', payload })
|
dispatch({ type: 'TOGGLE_NOTIFICATION_ALLOWED', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const toggleSocketConnected = payload => async (dispatch) => {
|
export const toggleSocketConnected = payload => async dispatch => {
|
||||||
dispatch({ type: 'TOGGLE_SOCKET_CONNECTED', payload })
|
dispatch({ type: 'TOGGLE_SOCKET_CONNECTED', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const createUser = payload => async (dispatch) => {
|
export const createUser = payload => async dispatch => {
|
||||||
dispatch({ type: 'CREATE_USER', payload })
|
dispatch({ type: 'CREATE_USER', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const clearActivities = () => async (dispatch) => {
|
export const clearActivities = () => async dispatch => {
|
||||||
dispatch({ type: 'CLEAR_ACTIVITIES' })
|
dispatch({ type: 'CLEAR_ACTIVITIES' });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const setLanguage = payload => async (dispatch) => {
|
export const setLanguage = payload => async dispatch => {
|
||||||
dispatch({type: 'CHANGE_LANGUAGE', payload});
|
dispatch({ type: 'CHANGE_LANGUAGE', payload });
|
||||||
}
|
};
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
import { getSocket } from 'utils/socket'
|
import { getSocket } from 'utils/socket';
|
||||||
import {
|
import { prepare as prepareMessage, process as processMessage } from 'utils/message';
|
||||||
prepare as prepareMessage,
|
|
||||||
process as processMessage,
|
|
||||||
} from 'utils/message'
|
|
||||||
|
|
||||||
export const sendEncryptedMessage = payload => async (dispatch, getState) => {
|
export const sendEncryptedMessage = payload => async (dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const msg = await prepareMessage(payload, state)
|
const msg = await prepareMessage(payload, state);
|
||||||
dispatch({ type: `SEND_ENCRYPTED_MESSAGE_${msg.original.type}`, payload: msg.original.payload })
|
dispatch({ type: `SEND_ENCRYPTED_MESSAGE_${msg.original.type}`, payload: msg.original.payload });
|
||||||
getSocket().emit('ENCRYPTED_MESSAGE', msg.toSend)
|
getSocket().emit('ENCRYPTED_MESSAGE', msg.toSend);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const receiveEncryptedMessage = payload => async (dispatch, getState) => {
|
export const receiveEncryptedMessage = payload => async (dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const message = await processMessage(payload, state)
|
const message = await processMessage(payload, state);
|
||||||
// Pass current state to all RECEIVE_ENCRYPTED_MESSAGE reducers for convenience, since each may have different needs
|
// Pass current state to all RECEIVE_ENCRYPTED_MESSAGE reducers for convenience, since each may have different needs
|
||||||
dispatch({ type: `RECEIVE_ENCRYPTED_MESSAGE_${message.type}`, payload: { payload: message.payload, state } })
|
dispatch({ type: `RECEIVE_ENCRYPTED_MESSAGE_${message.type}`, payload: { payload: message.payload, state } });
|
||||||
}
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
|
|
||||||
export * from './app'
|
export * from './app';
|
||||||
export * from './unencrypted_messages'
|
export * from './unencrypted_messages';
|
||||||
export * from './encrypted_messages'
|
export * from './encrypted_messages';
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { getSocket } from 'utils/socket'
|
import { getSocket } from 'utils/socket';
|
||||||
|
|
||||||
const receiveUserEnter = (payload, dispatch) => {
|
const receiveUserEnter = (payload, dispatch) => {
|
||||||
dispatch({ type: 'USER_ENTER', payload })
|
dispatch({ type: 'USER_ENTER', payload });
|
||||||
}
|
};
|
||||||
|
|
||||||
const receiveToggleLockRoom = (payload, dispatch, getState) => {
|
const receiveToggleLockRoom = (payload, dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
|
|
||||||
const lockedByUser = state.room.members.find(m => m.publicKey.n === payload.publicKey.n)
|
const lockedByUser = state.room.members.find(m => m.publicKey.n === payload.publicKey.n);
|
||||||
const lockedByUsername = lockedByUser.username
|
const lockedByUsername = lockedByUser.username;
|
||||||
const lockedByUserId = lockedByUser.id
|
const lockedByUserId = lockedByUser.id;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'RECEIVE_TOGGLE_LOCK_ROOM',
|
type: 'RECEIVE_TOGGLE_LOCK_ROOM',
|
||||||
@ -18,20 +18,20 @@ const receiveToggleLockRoom = (payload, dispatch, getState) => {
|
|||||||
locked: payload.locked,
|
locked: payload.locked,
|
||||||
id: lockedByUserId,
|
id: lockedByUserId,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const receiveUserExit = (payload, dispatch, getState) => {
|
const receiveUserExit = (payload, dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const payloadPublicKeys = payload.map(member => member.publicKey.n);
|
const payloadPublicKeys = payload.map(member => member.publicKey.n);
|
||||||
const exitingUser = state.room.members.find(m => !payloadPublicKeys.includes(m.publicKey.n))
|
const exitingUser = state.room.members.find(m => !payloadPublicKeys.includes(m.publicKey.n));
|
||||||
|
|
||||||
if (!exitingUser) {
|
if (!exitingUser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exitingUserId = exitingUser.id
|
const exitingUserId = exitingUser.id;
|
||||||
const exitingUsername = exitingUser.username
|
const exitingUsername = exitingUser.username;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'USER_EXIT',
|
type: 'USER_EXIT',
|
||||||
@ -40,11 +40,11 @@ const receiveUserExit = (payload, dispatch, getState) => {
|
|||||||
id: exitingUserId,
|
id: exitingUserId,
|
||||||
username: exitingUsername,
|
username: exitingUsername,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const receiveUnencryptedMessage = (type, payload) => async (dispatch, getState) => {
|
export const receiveUnencryptedMessage = (type, payload) => async (dispatch, getState) => {
|
||||||
switch(type) {
|
switch (type) {
|
||||||
case 'USER_ENTER':
|
case 'USER_ENTER':
|
||||||
return receiveUserEnter(payload, dispatch);
|
return receiveUserEnter(payload, dispatch);
|
||||||
case 'USER_EXIT':
|
case 'USER_EXIT':
|
||||||
@ -54,11 +54,11 @@ export const receiveUnencryptedMessage = (type, payload) => async (dispatch, get
|
|||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const sendToggleLockRoom = (dispatch, getState) => {
|
const sendToggleLockRoom = (dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
getSocket().emit('TOGGLE_LOCK_ROOM', null, (res) => {
|
getSocket().emit('TOGGLE_LOCK_ROOM', null, res => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'TOGGLE_LOCK_ROOM',
|
type: 'TOGGLE_LOCK_ROOM',
|
||||||
payload: {
|
payload: {
|
||||||
@ -66,15 +66,15 @@ const sendToggleLockRoom = (dispatch, getState) => {
|
|||||||
username: state.user.username,
|
username: state.user.username,
|
||||||
sender: state.user.id,
|
sender: state.user.id,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const sendUnencryptedMessage = (type, payload) => async (dispatch, getState) => {
|
export const sendUnencryptedMessage = (type, payload) => async (dispatch, getState) => {
|
||||||
switch(type) {
|
switch (type) {
|
||||||
case 'TOGGLE_LOCK_ROOM':
|
case 'TOGGLE_LOCK_ROOM':
|
||||||
return sendToggleLockRoom(dispatch, getState);
|
return sendToggleLockRoom(dispatch, getState);
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
let host
|
let host;
|
||||||
let protocol
|
let protocol;
|
||||||
let port
|
let port;
|
||||||
|
|
||||||
switch (process.env.NODE_ENV) {
|
switch (process.env.NODE_ENV) {
|
||||||
case 'staging':
|
case 'staging':
|
||||||
host = process.env.REACT_APP_API_HOST
|
host = process.env.REACT_APP_API_HOST;
|
||||||
protocol = process.env.REACT_APP_API_PROTOCOL || 'https'
|
protocol = process.env.REACT_APP_API_PROTOCOL || 'https';
|
||||||
port = process.env.REACT_APP_API_PORT || 443
|
port = process.env.REACT_APP_API_PORT || 443;
|
||||||
break
|
break;
|
||||||
case 'production':
|
case 'production':
|
||||||
host = process.env.REACT_APP_API_HOST
|
host = process.env.REACT_APP_API_HOST;
|
||||||
protocol = process.env.REACT_APP_API_PROTOCOL || 'https'
|
protocol = process.env.REACT_APP_API_PROTOCOL || 'https';
|
||||||
port = process.env.REACT_APP_API_PORT || 443
|
port = process.env.REACT_APP_API_PORT || 443;
|
||||||
break
|
break;
|
||||||
default:
|
default:
|
||||||
host = process.env.REACT_APP_API_HOST || 'localhost'
|
host = process.env.REACT_APP_API_HOST || 'localhost';
|
||||||
protocol = process.env.REACT_APP_API_PROTOCOL || 'http'
|
protocol = process.env.REACT_APP_API_PROTOCOL || 'http';
|
||||||
port = process.env.REACT_APP_API_PORT || 3001
|
port = process.env.REACT_APP_API_PORT || 3001;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
protocol,
|
protocol,
|
||||||
}
|
};
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
import config from './config'
|
import config from './config';
|
||||||
|
|
||||||
export default (resourceName = '') => {
|
export default (resourceName = '') => {
|
||||||
const { port, protocol, host } = config
|
const { port, protocol, host } = config;
|
||||||
|
|
||||||
const resourcePath = resourceName
|
const resourcePath = resourceName;
|
||||||
|
|
||||||
if (!host) {
|
if (!host) {
|
||||||
return `/${resourcePath}`;
|
return `/${resourcePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${protocol}://${host}:${port}/${resourcePath}`
|
return `${protocol}://${host}:${port}/${resourcePath}`;
|
||||||
}
|
};
|
||||||
|
@ -1,78 +1,125 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import apiUrlGenerator from '../../api/generator';
|
import apiUrlGenerator from '../../api/generator';
|
||||||
import styles from './styles.module.scss'
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
class About extends Component {
|
class About extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
roomId: props.roomId,
|
roomId: props.roomId,
|
||||||
abuseReported: false
|
abuseReported: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateRoomId(evt) {
|
handleUpdateRoomId(evt) {
|
||||||
this.setState({
|
this.setState({
|
||||||
roomId: evt.target.value,
|
roomId: evt.target.value,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReportAbuse(evt) {
|
handleReportAbuse(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
fetch(`${apiUrlGenerator('abuse')}/${this.state.roomId}`, {
|
fetch(`${apiUrlGenerator('abuse')}/${this.state.roomId}`, {
|
||||||
method: 'POST'
|
method: 'POST',
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
abuseReported: true,
|
abuseReported: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.base}>
|
<div className={styles.base}>
|
||||||
<div className={styles.links}>
|
<div className={styles.links}>
|
||||||
<div><a href="#version">Version</a></div>
|
<div>
|
||||||
<div><a href="#software">Software</a></div>
|
<a href="#version">Version</a>
|
||||||
<div><a href="#report-abuse">Report Abuse</a></div>
|
</div>
|
||||||
<div><a href="#acceptable-use">Acceptable Use Policy</a></div>
|
<div>
|
||||||
<div><a href="#disclaimer">Disclaimer</a></div>
|
<a href="#software">Software</a>
|
||||||
<div><a href="#terms">Terms of Service</a></div>
|
</div>
|
||||||
<div><a href="#contact">Contact</a></div>
|
<div>
|
||||||
<div><a href="#donate">Donate</a></div>
|
<a href="#report-abuse">Report Abuse</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="#acceptable-use">Acceptable Use Policy</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="#disclaimer">Disclaimer</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="#terms">Terms of Service</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="#contact">Contact</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="#donate">Donate</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id='version'>
|
<section id="version">
|
||||||
<h4>Version</h4>
|
<h4>Version</h4>
|
||||||
<p>
|
<p>
|
||||||
Commit SHA: <a target="_blank" href={`https://github.com/darkwire/darkwire.io/commit/${process.env.REACT_APP_COMMIT_SHA}`}>{process.env.REACT_APP_COMMIT_SHA}</a></p>
|
Commit SHA:{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href={`https://github.com/darkwire/darkwire.io/commit/${process.env.REACT_APP_COMMIT_SHA}`}
|
||||||
|
>
|
||||||
|
{process.env.REACT_APP_COMMIT_SHA}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id='software'>
|
<section id="software">
|
||||||
<h4>Software</h4>
|
<h4>Software</h4>
|
||||||
<p>This software uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Crypto" target="_blank" rel="noopener noreferrer">Web Cryptography API</a> to
|
<p>
|
||||||
encrypt data which is transferred using <a href="https://en.wikipedia.org/wiki/WebSocket" target="_blank" rel="noopener noreferrer">secure WebSockets</a>.
|
This software uses the{' '}
|
||||||
Messages are never stored on a server or sent over the wire in plain-text.</p>
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Crypto" target="_blank" rel="noopener noreferrer">
|
||||||
<p>We believe in privacy and transparency.
|
Web Cryptography API
|
||||||
<a href="https://github.com/darkwire/darkwire.io" target="_blank" rel="noopener noreferrer">View the source code and documentation on GitHub.</a></p>
|
</a>{' '}
|
||||||
|
to encrypt data which is transferred using{' '}
|
||||||
|
<a href="https://en.wikipedia.org/wiki/WebSocket" target="_blank" rel="noopener noreferrer">
|
||||||
|
secure WebSockets
|
||||||
|
</a>
|
||||||
|
. Messages are never stored on a server or sent over the wire in plain-text.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We believe in privacy and transparency.
|
||||||
|
<a href="https://github.com/darkwire/darkwire.io" target="_blank" rel="noopener noreferrer">
|
||||||
|
View the source code and documentation on GitHub.
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id='report-abuse'>
|
<section id="report-abuse">
|
||||||
<h4>Report Abuse</h4>
|
<h4>Report Abuse</h4>
|
||||||
<p>We encourage you to report problematic content to us. Please keep in mind that to help ensure the safety, confidentiality and security of your messages, we do not have the contents of messages available to us, which limits our ability to verify the report and take action.</p>
|
<p>
|
||||||
<p>When needed, you can take a screenshot of the content and share it, along with any available contact info, with appropriate law enforcement authorities.</p>
|
We encourage you to report problematic content to us. Please keep in mind that to help ensure the safety,
|
||||||
<p>To report any content, email us at abuse[at]darkwire.io or submit the room ID below to report anonymously.</p>
|
confidentiality and security of your messages, we do not have the contents of messages available to us,
|
||||||
|
which limits our ability to verify the report and take action.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
When needed, you can take a screenshot of the content and share it, along with any available contact info,
|
||||||
|
with appropriate law enforcement authorities.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To report any content, email us at abuse[at]darkwire.io or submit the room ID below to report anonymously.
|
||||||
|
</p>
|
||||||
<form onSubmit={this.handleReportAbuse.bind(this)}>
|
<form onSubmit={this.handleReportAbuse.bind(this)}>
|
||||||
{this.state.abuseReported && <div>Thank you!</div>}
|
{this.state.abuseReported && <div>Thank you!</div>}
|
||||||
<div>
|
<div>
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<input className='form-control' placeholder='Room ID' onChange={this.handleUpdateRoomId.bind(this)} value={this.state.roomId} type="text"/>
|
<input
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Room ID"
|
||||||
|
onChange={this.handleUpdateRoomId.bind(this)}
|
||||||
|
value={this.state.roomId}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
<div className="input-group-append">
|
<div className="input-group-append">
|
||||||
<button
|
<button className="btn btn-secondary" type="submit">
|
||||||
className="btn btn-secondary"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -81,128 +128,296 @@ class About extends Component {
|
|||||||
</form>
|
</form>
|
||||||
<br />
|
<br />
|
||||||
<p>If you feel you or anyone else is in immediate danger, please contact your local emergency services.</p>
|
<p>If you feel you or anyone else is in immediate danger, please contact your local emergency services.</p>
|
||||||
<p>If you receive content from someone who wishes to hurt themselves, and you're concerned for their safety, please contact your local emergency services or a <a href="https://faq.whatsapp.com/en/general/28030010">suicide prevention hotline</a>.</p>
|
<p>
|
||||||
<p>If you receive or encounter content indicating abuse or exploitation of a child, please contact the <a href="http://www.missingkids.com">National Center for Missing and Exploited Children (NCMEC)</a>.</p>
|
If you receive content from someone who wishes to hurt themselves, and you're concerned for their safety,
|
||||||
|
please contact your local emergency services or a{' '}
|
||||||
|
<a href="https://faq.whatsapp.com/en/general/28030010">suicide prevention hotline</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you receive or encounter content indicating abuse or exploitation of a child, please contact the{' '}
|
||||||
|
<a href="http://www.missingkids.com">National Center for Missing and Exploited Children (NCMEC)</a>.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id='acceptable-use'>
|
<section id="acceptable-use">
|
||||||
<h4>Acceptable Use Policy</h4>
|
<h4>Acceptable Use Policy</h4>
|
||||||
|
<p>
|
||||||
<p>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.</p>
|
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.
|
||||||
|
</p>
|
||||||
<strong>No Illegal, Harmful, or Offensive Use or Content</strong>
|
<strong>No Illegal, Harmful, or Offensive Use or Content</strong>
|
||||||
<p>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:</p>
|
<p>
|
||||||
|
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:
|
||||||
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Illegal, Harmful or Fraudulent Activities.</strong> 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.</li>
|
<li>
|
||||||
|
<strong>Illegal, Harmful or Fraudulent Activities.</strong> Any activities that are illegal, that violate
|
||||||
<li><strong>Infringing Content.</strong> Content that infringes or misappropriates the intellectual property or proprietary rights of others.</li>
|
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,
|
||||||
<li><strong>Offensive Content.</strong> 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.</li>
|
services, schemes, or promotions, make-money-fast schemes, ponzi and pyramid schemes, phishing, or
|
||||||
|
pharming.
|
||||||
<li><strong>Harmful Content.</strong> 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.</li>
|
</li>
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Infringing Content.</strong> Content that infringes or misappropriates the intellectual property
|
||||||
|
or proprietary rights of others.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Offensive Content.</strong> 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.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Harmful Content.</strong> 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.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<strong>No Security Violations</strong>
|
<strong>No Security Violations</strong>
|
||||||
<br/>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:
|
<br />
|
||||||
|
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:
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Unauthorized Access.</strong> 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.</li>
|
<li>
|
||||||
|
<strong>Unauthorized Access.</strong> Accessing or using any System without permission, including
|
||||||
<li><strong>Interception.</strong> Monitoring of data or traffic on a System without permission.</li>
|
attempting to probe, scan, or test the vulnerability of a System or to breach any security or
|
||||||
|
authentication measures used by a System.
|
||||||
<li><strong>Falsification of Origin.</strong> 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.</li>
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Interception.</strong> Monitoring of data or traffic on a System without permission.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Falsification of Origin.</strong> 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.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<strong>No Network Abuse</strong>
|
<strong>No Network Abuse</strong>
|
||||||
<br/>You may not make network connections to any users, hosts, or networks unless you have permission to communicate with them. Prohibited activities include:
|
<br />
|
||||||
|
You may not make network connections to any users, hosts, or networks unless you have permission to
|
||||||
|
communicate with them. Prohibited activities include:
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Monitoring or Crawling.</strong> Monitoring or crawling of a System that impairs or disrupts the System being monitored or crawled.</li>
|
<li>
|
||||||
|
<strong>Monitoring or Crawling.</strong> Monitoring or crawling of a System that impairs or disrupts the
|
||||||
<li><strong>Denial of Service (DoS).</strong> Inundating a target with communications requests so the target either cannot respond to legitimate traffic or responds so slowly that it becomes ineffective.</li>
|
System being monitored or crawled.
|
||||||
|
</li>
|
||||||
<li><strong>Intentional Interference.</strong> 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.</li>
|
|
||||||
|
<li>
|
||||||
<li><strong>Operation of Certain Network Services.</strong> Operating network services like open proxies, open mail relays, or open recursive domain name servers.</li>
|
<strong>Denial of Service (DoS).</strong> Inundating a target with communications requests so the target
|
||||||
|
either cannot respond to legitimate traffic or responds so slowly that it becomes ineffective.
|
||||||
<li><strong>Avoiding System Restrictions.</strong> Using manual or electronic means to avoid any use limitations placed on a System, such as access and storage restrictions.</li>
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Intentional Interference.</strong> 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.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Operation of Certain Network Services.</strong> Operating network services like open proxies, open
|
||||||
|
mail relays, or open recursive domain name servers.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<strong>Avoiding System Restrictions.</strong> Using manual or electronic means to avoid any use
|
||||||
|
limitations placed on a System, such as access and storage restrictions.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<strong>No E-Mail or Other Message Abuse</strong>
|
<strong>No E-Mail or Other Message Abuse</strong>
|
||||||
<br/>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.
|
<br />
|
||||||
|
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.
|
||||||
<strong>Our Monitoring and Enforcement</strong>
|
<strong>Our Monitoring and Enforcement</strong>
|
||||||
<br/>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:
|
<br />
|
||||||
|
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:
|
||||||
<ul>
|
<ul>
|
||||||
<li>investigate violations of this Policy or misuse of the Services or Darkwire Site; or</li>
|
<li>investigate violations of this Policy or misuse of the Services or Darkwire Site; or</li>
|
||||||
<li>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.</li>
|
<li>
|
||||||
<li>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.</li>
|
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.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Reporting of Violations of this Policy
|
Reporting of Violations of this Policy
|
||||||
<br/>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.
|
<br />
|
||||||
|
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.
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id='terms'>
|
<section id="terms">
|
||||||
<h4>Terms of Service ("Terms")</h4>
|
<h4>Terms of Service ("Terms")</h4>
|
||||||
<p>Last updated: December 11, 2017</p>
|
<p>Last updated: December 11, 2017</p>
|
||||||
<p>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").</p>
|
<p>
|
||||||
<p>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.</p>
|
Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the
|
||||||
<p>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.</p>
|
https://darkwire.io website (the "Service") operated by Darkwire ("us", "we", or "our").
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
<strong>Links To Other Web Sites</strong>
|
<strong>Links To Other Web Sites</strong>
|
||||||
<p>Our Service may contain links to third-party web sites or services that are not owned or controlled by Darkwire.</p>
|
<p>
|
||||||
<p>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.</p>
|
Our Service may contain links to third-party web sites or services that are not owned or controlled by
|
||||||
<p>We strongly advise you to read the terms and conditions and privacy policies of any third-party web sites or services that you visit.</p>
|
Darkwire.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We strongly advise you to read the terms and conditions and privacy policies of any third-party web sites or
|
||||||
|
services that you visit.
|
||||||
|
</p>
|
||||||
<strong>Termination</strong>
|
<strong>Termination</strong>
|
||||||
<p>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.</p>
|
<p>
|
||||||
<p>All provisions of the Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions,
|
We may terminate or suspend access to our Service immediately, without prior notice or liability, for any
|
||||||
warranty disclaimers, indemnity and limitations of liability.</p>
|
reason whatsoever, including without limitation if you breach the Terms.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
<strong>Governing Law</strong>
|
<strong>Governing Law</strong>
|
||||||
<p>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.</p>
|
<p>
|
||||||
<p>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
|
These Terms shall be governed and construed in accordance with the laws of New York, United States, without
|
||||||
invalid or unenforceable by a court, the remaining provisions of these Terms will remain in effect. These Terms constitute the entire agreement between us
|
regard to its conflict of law provisions.
|
||||||
regarding our Service, and supersede and replace any prior agreements we might have between us regarding the Service.</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id='disclaimer'>
|
<section id="disclaimer">
|
||||||
<h4>Disclaimer</h4>
|
<h4>Disclaimer</h4>
|
||||||
<p className="bold">WARNING: Darkwire does not mask IP addresses nor can verify the integrity of parties recieving messages.
|
<p className="bold">
|
||||||
Proceed with caution and always confirm recipients beforre starting a chat session.</p>
|
WARNING: Darkwire does not mask IP addresses nor can verify the integrity of parties recieving messages.
|
||||||
<p>Please also note that <strong>ALL CHATROOMS</strong> are public.
|
Proceed with caution and always confirm recipients beforre starting a chat session.
|
||||||
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/".
|
</p>
|
||||||
|
<p>
|
||||||
|
Please also note that <strong>ALL CHATROOMS</strong> 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/".
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<strong>No Warranties; Exclusion of Liability; Indemnification</strong>
|
<strong>No Warranties; Exclusion of Liability; Indemnification</strong>
|
||||||
<p><strong>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,
|
<p>
|
||||||
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.</strong></p>
|
<strong>
|
||||||
<p><strong>SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO YOU.</strong></p>
|
OUR WEBSITE IS OPERATED BY Darkwire ON AN "AS IS," "AS AVAILABLE" BASIS, WITHOUT REPRESENTATIONS OR
|
||||||
<p><strong>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.</strong></p>
|
WARRANTIES OF ANY KIND. TO THE FULLEST EXTENT PERMITTED BY LAW, Darkwire SPECIFICALLY DISCLAIMS ALL
|
||||||
<p><strong>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.</strong></p>
|
WARRANTIES AND CONDITIONS OF ANY KIND, INCLUDING ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY,
|
||||||
<p><strong>Darkwire MAKES NO REPRESENTATION THAT CONTENT PROVIDED ON OUR WEBSITE, CONTRACTS, OR RELATED SERVICES ARE APPLICABLE OR APPROPRIATE FOR USE IN ALL
|
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NONINFRINGEMENT FOR OUR WEBSITE AND ANY CONTRACTS AND SERVICES
|
||||||
JURISDICTIONS.</strong></p>
|
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.
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE
|
||||||
|
ABOVE EXCLUSION MAY NOT APPLY TO YOU.
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
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.
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
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.
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
Darkwire MAKES NO REPRESENTATION THAT CONTENT PROVIDED ON OUR WEBSITE, CONTRACTS, OR RELATED SERVICES ARE
|
||||||
|
APPLICABLE OR APPROPRIATE FOR USE IN ALL JURISDICTIONS.
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
<strong>Indemnification</strong>
|
<strong>Indemnification</strong>
|
||||||
<p>You agree to defend, indemnify and hold Darkwire harmless from and against any and all claims, damages, costs and expenses, including attorneys' fees, arising
|
<p>
|
||||||
from or related to your use of our Website or any Contracts or Services you purchase through it.</p>
|
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.
|
||||||
|
</p>
|
||||||
<strong>Changes</strong>
|
<strong>Changes</strong>
|
||||||
<p>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.</p>
|
<p>
|
||||||
<p>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
|
We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is
|
||||||
terms, please stop using the Service.</p>
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
<strong>Contact Us</strong>
|
<strong>Contact Us</strong>
|
||||||
<p>If you have any questions about these Terms, please contact us at hello[at]darkwire.io.</p>
|
<p>If you have any questions about these Terms, please contact us at hello[at]darkwire.io.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id='contact'>
|
<section id="contact">
|
||||||
<h4>Contact</h4>
|
<h4>Contact</h4>
|
||||||
<p>Questions/comments? Email us at hello[at]darkwire.io</p>
|
<p>Questions/comments? Email us at hello[at]darkwire.io</p>
|
||||||
<p>Found a bug or want a new feature? <a href="https://github.com/darkwire/darkwire.io/issues" target="_blank" rel="noopener noreferrer">Open a ticket on Github</a>.</p>
|
<p>
|
||||||
|
Found a bug or want a new feature?{' '}
|
||||||
|
<a href="https://github.com/darkwire/darkwire.io/issues" target="_blank" rel="noopener noreferrer">
|
||||||
|
Open a ticket on Github
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id='donate'>
|
<section id="donate">
|
||||||
<h4>Donate</h4>
|
<h4>Donate</h4>
|
||||||
<p>Darkwire is maintained and hosted by two developers with full-time jobs. If you get some value
|
<p>
|
||||||
from this service we would appreciate any donation you can afford. We use these funds for
|
Darkwire is maintained and hosted by two developers with full-time jobs. If you get some value from this
|
||||||
server and DNS costs. Thank you!
|
service we would appreciate any donation you can afford. We use these funds for server and DNS costs. Thank
|
||||||
|
you!
|
||||||
</p>
|
</p>
|
||||||
<strong>Bitcoin</strong>
|
<strong>Bitcoin</strong>
|
||||||
<p>189sPnHGcjP5uteg2UuNgcJ5eoaRAP4Bw4</p>
|
<p>189sPnHGcjP5uteg2UuNgcJ5eoaRAP4Bw4</p>
|
||||||
@ -215,17 +430,23 @@ class About extends Component {
|
|||||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
|
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
|
||||||
<input type="hidden" name="cmd" value="_s-xclick" />
|
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||||
<input type="hidden" name="hosted_button_id" value="UAH5BCLA9Y8VW" />
|
<input type="hidden" name="hosted_button_id" value="UAH5BCLA9Y8VW" />
|
||||||
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" />
|
<input
|
||||||
|
type="image"
|
||||||
|
src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif"
|
||||||
|
border="0"
|
||||||
|
name="submit"
|
||||||
|
alt="PayPal - The safer, easier way to pay online!"
|
||||||
|
/>
|
||||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1" />
|
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1" />
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
About.propTypes = {
|
About.propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default About
|
export default About;
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import Chat from './Chat'
|
import Chat from './Chat';
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux';
|
||||||
import { clearActivities, showNotice, sendEncryptedMessage } from '../../actions'
|
import { clearActivities, showNotice, sendEncryptedMessage } from '../../actions';
|
||||||
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
username: state.user.username,
|
username: state.user.username,
|
||||||
userId: state.user.id,
|
userId: state.user.id,
|
||||||
translations: state.app.translations,
|
translations: state.app.translations,
|
||||||
})
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
clearActivities,
|
clearActivities,
|
||||||
showNotice,
|
showNotice,
|
||||||
sendEncryptedMessage
|
sendEncryptedMessage,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(mapStateToProps, mapDispatchToProps)(Chat);
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Chat)
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react';
|
||||||
import Connecting from '.'
|
import Connecting from '.';
|
||||||
|
|
||||||
test('Connecting component is displaying', async () => {
|
test('Connecting component is displaying', async () => {
|
||||||
const {asFragment} = render(<Connecting />)
|
const { asFragment } = render(<Connecting />);
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot()
|
expect(asFragment()).toMatchSnapshot();
|
||||||
})
|
});
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
export default class Connecting extends Component {
|
export default class Connecting extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return <div>Please wait while we secure a connection to Darkwire...</div>;
|
||||||
<div>
|
|
||||||
Please wait while we secure a connection to Darkwire...
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,106 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import uuid from 'uuid'
|
import uuid from 'uuid';
|
||||||
import { File } from 'react-feather'
|
import { File } from 'react-feather';
|
||||||
import { sanitize } from 'utils'
|
import { sanitize } from 'utils';
|
||||||
import { styles } from './styles.module.scss'
|
import { styles } from './styles.module.scss';
|
||||||
|
|
||||||
const VALID_FILE_TYPES = ['png', 'jpg', 'jpeg', 'gif', 'zip', 'rar', 'gzip', 'pdf', 'txt', 'json', 'doc', 'docx', 'csv', 'js', 'html', 'css']
|
const VALID_FILE_TYPES = [
|
||||||
|
'png',
|
||||||
|
'jpg',
|
||||||
|
'jpeg',
|
||||||
|
'gif',
|
||||||
|
'zip',
|
||||||
|
'rar',
|
||||||
|
'gzip',
|
||||||
|
'pdf',
|
||||||
|
'txt',
|
||||||
|
'json',
|
||||||
|
'doc',
|
||||||
|
'docx',
|
||||||
|
'csv',
|
||||||
|
'js',
|
||||||
|
'html',
|
||||||
|
'css',
|
||||||
|
];
|
||||||
|
|
||||||
const MAX_FILE_SIZE = process.env.REACT_APP_MAX_FILE_SIZE || 4
|
const MAX_FILE_SIZE = process.env.REACT_APP_MAX_FILE_SIZE || 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode the given file to binary string
|
* Encode the given file to binary string
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
*/
|
*/
|
||||||
const encodeFile = (file) => {
|
const encodeFile = file => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new window.FileReader()
|
const reader = new window.FileReader();
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
reject()
|
reject();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.onload = (readerEvent) => {
|
reader.onload = readerEvent => {
|
||||||
resolve(window.btoa(readerEvent.target.result))
|
resolve(window.btoa(readerEvent.target.result));
|
||||||
}
|
};
|
||||||
|
|
||||||
reader.readAsBinaryString(file)
|
reader.readAsBinaryString(file);
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const FileTransfer = ({ sendEncryptedMessage }) => {
|
export const FileTransfer = ({ sendEncryptedMessage }) => {
|
||||||
const fileInput = React.useRef(null);
|
const fileInput = React.useRef(null);
|
||||||
|
|
||||||
const supported = React.useMemo(() =>
|
const supported = React.useMemo(
|
||||||
Boolean(window.File) && Boolean(window.FileReader) && Boolean(window.FileList) && Boolean(window.Blob) &&
|
() =>
|
||||||
Boolean(window.btoa) && Boolean(window.atob) && Boolean(window.URL),
|
Boolean(window.File) &&
|
||||||
[]
|
Boolean(window.FileReader) &&
|
||||||
)
|
Boolean(window.FileList) &&
|
||||||
|
Boolean(window.Blob) &&
|
||||||
|
Boolean(window.btoa) &&
|
||||||
|
Boolean(window.atob) &&
|
||||||
|
Boolean(window.URL),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const currentFileInput = fileInput.current
|
const currentFileInput = fileInput.current;
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
|
||||||
const handleFileTransfer = async (event) => {
|
const handleFileTransfer = async event => {
|
||||||
|
const file = event.target.files && event.target.files[0];
|
||||||
const file = event.target.files && event.target.files[0]
|
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
const fileType = file.type || 'file'
|
const fileType = file.type || 'file';
|
||||||
const fileName = sanitize(file.name)
|
const fileName = sanitize(file.name);
|
||||||
const fileExtension = file.name.split('.').pop().toLowerCase()
|
const fileExtension = file.name.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
if (VALID_FILE_TYPES.indexOf(fileExtension) <= -1) {
|
if (VALID_FILE_TYPES.indexOf(fileExtension) <= -1) {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
alert('File type not supported')
|
alert('File type not supported');
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size > MAX_FILE_SIZE * 1000000) {
|
if (file.size > MAX_FILE_SIZE * 1000000) {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
alert(`Max filesize is ${MAX_FILE_SIZE}MB`)
|
alert(`Max filesize is ${MAX_FILE_SIZE}MB`);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileId = uuid.v4()
|
const fileId = uuid.v4();
|
||||||
const fileData = {
|
const fileData = {
|
||||||
id: fileId,
|
id: fileId,
|
||||||
file,
|
file,
|
||||||
fileName,
|
fileName,
|
||||||
fileType,
|
fileType,
|
||||||
encodedFile: await encodeFile(file),
|
encodedFile: await encodeFile(file),
|
||||||
}
|
};
|
||||||
|
|
||||||
// Mounted component guard
|
// Mounted component guard
|
||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInput.current.value = ''
|
fileInput.current.value = '';
|
||||||
|
|
||||||
sendEncryptedMessage({
|
sendEncryptedMessage({
|
||||||
type: 'SEND_FILE',
|
type: 'SEND_FILE',
|
||||||
@ -88,22 +110,22 @@ export const FileTransfer = ({ sendEncryptedMessage }) => {
|
|||||||
fileType: fileData.fileType,
|
fileType: fileData.fileType,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
currentFileInput.addEventListener('change', handleFileTransfer)
|
currentFileInput.addEventListener('change', handleFileTransfer);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false
|
isMounted = false;
|
||||||
currentFileInput.removeEventListener('change', handleFileTransfer)
|
currentFileInput.removeEventListener('change', handleFileTransfer);
|
||||||
}
|
};
|
||||||
}, [sendEncryptedMessage])
|
}, [sendEncryptedMessage]);
|
||||||
|
|
||||||
if (!supported) {
|
if (!supported) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -114,10 +136,10 @@ export const FileTransfer = ({ sendEncryptedMessage }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
FileTransfer.propTypes = {
|
FileTransfer.propTypes = {
|
||||||
sendEncryptedMessage: PropTypes.func.isRequired,
|
sendEncryptedMessage: PropTypes.func.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default FileTransfer
|
export default FileTransfer;
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import Message from 'components/Message'
|
import Message from 'components/Message';
|
||||||
import Username from 'components/Username'
|
import Username from 'components/Username';
|
||||||
import Notice from 'components/Notice'
|
import Notice from 'components/Notice';
|
||||||
import Zoom from 'utils/ImageZoom'
|
import Zoom from 'utils/ImageZoom';
|
||||||
import { getObjectUrl } from 'utils/file'
|
import { getObjectUrl } from 'utils/file';
|
||||||
|
|
||||||
import T from 'components/T'
|
import T from 'components/T';
|
||||||
|
|
||||||
const FileDisplay = ({ activity: { fileType, encodedFile, fileName, username }, scrollToBottom }) => {
|
const FileDisplay = ({ activity: { fileType, encodedFile, fileName, username }, scrollToBottom }) => {
|
||||||
const zoomableImage = React.useRef(null)
|
const zoomableImage = React.useRef(null);
|
||||||
|
|
||||||
const handleImageDisplay = () => {
|
const handleImageDisplay = () => {
|
||||||
Zoom(zoomableImage.current)
|
Zoom(zoomableImage.current);
|
||||||
scrollToBottom()
|
scrollToBottom();
|
||||||
}
|
};
|
||||||
|
|
||||||
if (fileType.match('image.*')) {
|
if (fileType.match('image.*')) {
|
||||||
return (
|
return (
|
||||||
@ -25,117 +25,147 @@ const FileDisplay = ({ activity: { fileType, encodedFile, fileName, username },
|
|||||||
alt={`${fileName} from ${username}`}
|
alt={`${fileName} from ${username}`}
|
||||||
onLoad={handleImageDisplay}
|
onLoad={handleImageDisplay}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
const Activity = ({ activity, scrollToBottom }) => {
|
const Activity = ({ activity, scrollToBottom }) => {
|
||||||
switch (activity.type) {
|
switch (activity.type) {
|
||||||
case 'TEXT_MESSAGE':
|
case 'TEXT_MESSAGE':
|
||||||
return (
|
return <Message sender={activity.username} message={activity.text} timestamp={activity.timestamp} />;
|
||||||
<Message
|
|
||||||
sender={activity.username}
|
|
||||||
message={activity.text}
|
|
||||||
timestamp={activity.timestamp}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'USER_ENTER':
|
case 'USER_ENTER':
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div>
|
<div>
|
||||||
<T data={{
|
<T
|
||||||
username: <Username key={0} username={activity.username} />
|
data={{
|
||||||
}} path='userJoined'/>
|
username: <Username key={0} username={activity.username} />,
|
||||||
|
}}
|
||||||
|
path="userJoined"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
case 'USER_EXIT':
|
case 'USER_EXIT':
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div>
|
<div>
|
||||||
<T data={{
|
<T
|
||||||
username: <Username key={0} username={activity.username} />
|
data={{
|
||||||
}} path='userLeft'/>
|
username: <Username key={0} username={activity.username} />,
|
||||||
|
}}
|
||||||
|
path="userLeft"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
case 'TOGGLE_LOCK_ROOM':
|
case 'TOGGLE_LOCK_ROOM':
|
||||||
if (activity.locked) {
|
if (activity.locked) {
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div><T data={{
|
<div>
|
||||||
username: <Username key={0} username={activity.username} />
|
<T
|
||||||
}} path='lockedRoom'/></div>
|
data={{
|
||||||
|
username: <Username key={0} username={activity.username} />,
|
||||||
|
}}
|
||||||
|
path="lockedRoom"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div><T data={{
|
<div>
|
||||||
username: <Username key={0} username={activity.username} />
|
<T
|
||||||
}} path='unlockedRoom'/></div>
|
data={{
|
||||||
|
username: <Username key={0} username={activity.username} />,
|
||||||
|
}}
|
||||||
|
path="unlockedRoom"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
case 'NOTICE':
|
case 'NOTICE':
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div>{activity.message}</div>
|
<div>{activity.message}</div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
case 'CHANGE_USERNAME':
|
case 'CHANGE_USERNAME':
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div><T data={{
|
<div>
|
||||||
oldUsername: <Username key={0} username={activity.currentUsername} />,
|
<T
|
||||||
newUsername: <Username key={1} username={activity.newUsername} />
|
data={{
|
||||||
}} path='nameChange'/>
|
oldUsername: <Username key={0} username={activity.currentUsername} />,
|
||||||
|
newUsername: <Username key={1} username={activity.newUsername} />,
|
||||||
|
}}
|
||||||
|
path="nameChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
case 'USER_ACTION':
|
case 'USER_ACTION':
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div>* <Username username={activity.username} /> {activity.action}</div>
|
<div>
|
||||||
|
* <Username username={activity.username} /> {activity.action}
|
||||||
|
</div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
case 'RECEIVE_FILE':
|
case 'RECEIVE_FILE':
|
||||||
const downloadUrl = getObjectUrl(activity.encodedFile, activity.fileType)
|
const downloadUrl = getObjectUrl(activity.encodedFile, activity.fileType);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<T data={{
|
<T
|
||||||
username: <Username key={0} username={activity.username} />,
|
data={{
|
||||||
}} path='userSentFile'/>
|
username: <Username key={0} username={activity.username} />,
|
||||||
|
}}
|
||||||
<a target="_blank" href={downloadUrl} rel="noopener noreferrer" download={activity.fileName} >
|
path="userSentFile"
|
||||||
<T data={{
|
/>
|
||||||
filename: activity.fileName,
|
|
||||||
}} path='downloadFile'/>
|
<a target="_blank" href={downloadUrl} rel="noopener noreferrer" download={activity.fileName}>
|
||||||
|
<T
|
||||||
|
data={{
|
||||||
|
filename: activity.fileName,
|
||||||
|
}}
|
||||||
|
path="downloadFile"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<FileDisplay activity={activity} scrollToBottom={scrollToBottom} />
|
<FileDisplay activity={activity} scrollToBottom={scrollToBottom} />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
case 'SEND_FILE':
|
case 'SEND_FILE':
|
||||||
const url = getObjectUrl(activity.encodedFile, activity.fileType)
|
const url = getObjectUrl(activity.encodedFile, activity.fileType);
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div>
|
<div>
|
||||||
<T data={{
|
<T
|
||||||
filename: <a key={0} target="_blank" href={url} rel="noopener noreferrer" download={activity.fileName}>{activity.fileName}</a>,
|
data={{
|
||||||
}} path='sentFile'/>
|
filename: (
|
||||||
|
<a key={0} target="_blank" href={url} rel="noopener noreferrer" download={activity.fileName}>
|
||||||
|
{activity.fileName}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
path="sentFile"
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<FileDisplay activity={activity} scrollToBottom={scrollToBottom} />
|
<FileDisplay activity={activity} scrollToBottom={scrollToBottom} />
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
);
|
||||||
default:
|
default:
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Activity.propTypes = {
|
Activity.propTypes = {
|
||||||
activity: PropTypes.object.isRequired,
|
activity: PropTypes.object.isRequired,
|
||||||
scrollToBottom: PropTypes.func.isRequired,
|
scrollToBottom: PropTypes.func.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Activity
|
export default Activity;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import ChatInput from 'components/Chat'
|
import ChatInput from 'components/Chat';
|
||||||
import Activity from './Activity'
|
import Activity from './Activity';
|
||||||
import T from 'components/T'
|
import T from 'components/T';
|
||||||
import { defer } from 'lodash'
|
import { defer } from 'lodash';
|
||||||
|
|
||||||
import styles from './styles.module.scss'
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
const ActivityList = ({ activities, openModal }) => {
|
const ActivityList = ({ activities, openModal }) => {
|
||||||
const [focusChat, setFocusChat] = React.useState(false);
|
const [focusChat, setFocusChat] = React.useState(false);
|
||||||
@ -18,56 +18,63 @@ const ActivityList = ({ activities, openModal }) => {
|
|||||||
|
|
||||||
// Update scrolledToBottom state if we scroll the activity stream
|
// Update scrolledToBottom state if we scroll the activity stream
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
const messageStreamHeight = messageStream.current.clientHeight
|
const messageStreamHeight = messageStream.current.clientHeight;
|
||||||
const activitiesListHeight = activitiesList.current.clientHeight
|
const activitiesListHeight = activitiesList.current.clientHeight;
|
||||||
|
|
||||||
const bodyRect = document.body.getBoundingClientRect()
|
const bodyRect = document.body.getBoundingClientRect();
|
||||||
const elemRect = activitiesList.current.getBoundingClientRect()
|
const elemRect = activitiesList.current.getBoundingClientRect();
|
||||||
const offset = elemRect.top - bodyRect.top
|
const offset = elemRect.top - bodyRect.top;
|
||||||
const activitiesListYPos = offset
|
const activitiesListYPos = offset;
|
||||||
|
|
||||||
const newScrolledToBottom = (activitiesListHeight + (activitiesListYPos - 60)) <= messageStreamHeight
|
const newScrolledToBottom = activitiesListHeight + (activitiesListYPos - 60) <= messageStreamHeight;
|
||||||
if (newScrolledToBottom) {
|
if (newScrolledToBottom) {
|
||||||
if (!scrolledToBottom) {
|
if (!scrolledToBottom) {
|
||||||
setScrolledToBottom(true)
|
setScrolledToBottom(true);
|
||||||
}
|
}
|
||||||
} else if (scrolledToBottom) {
|
} else if (scrolledToBottom) {
|
||||||
setScrolledToBottom(false)
|
setScrolledToBottom(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
currentMessageStream.addEventListener('scroll', onScroll)
|
currentMessageStream.addEventListener('scroll', onScroll);
|
||||||
return () => {
|
return () => {
|
||||||
// Unbind event if component unmounted
|
// Unbind event if component unmounted
|
||||||
currentMessageStream.removeEventListener('scroll', onScroll)
|
currentMessageStream.removeEventListener('scroll', onScroll);
|
||||||
}
|
};
|
||||||
}, [scrolledToBottom])
|
}, [scrolledToBottom]);
|
||||||
|
|
||||||
const scrollToBottomIfShould = React.useCallback(() => {
|
const scrollToBottomIfShould = React.useCallback(() => {
|
||||||
if (scrolledToBottom) {
|
if (scrolledToBottom) {
|
||||||
messageStream.current.scrollTop = messageStream.current.scrollHeight
|
messageStream.current.scrollTop = messageStream.current.scrollHeight;
|
||||||
}
|
}
|
||||||
}, [scrolledToBottom])
|
}, [scrolledToBottom]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
scrollToBottomIfShould(); // Only if activities.length bigger
|
scrollToBottomIfShould(); // Only if activities.length bigger
|
||||||
}, [scrollToBottomIfShould, activities]);
|
}, [scrollToBottomIfShould, activities]);
|
||||||
|
|
||||||
const scrollToBottom = React.useCallback(() => {
|
const scrollToBottom = React.useCallback(() => {
|
||||||
messageStream.current.scrollTop = messageStream.current.scrollHeight
|
messageStream.current.scrollTop = messageStream.current.scrollHeight;
|
||||||
setScrolledToBottom(true)
|
setScrolledToBottom(true);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleChatClick = () => {
|
const handleChatClick = () => {
|
||||||
setFocusChat(true);
|
setFocusChat(true);
|
||||||
defer(() => setFocusChat(false))
|
defer(() => setFocusChat(false));
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="main-chat">
|
<div className="main-chat">
|
||||||
<div onClick={handleChatClick} className="message-stream h-100" ref={messageStream} data-testid="main-div">
|
<div onClick={handleChatClick} className="message-stream h-100" ref={messageStream} data-testid="main-div">
|
||||||
<ul className="plain" ref={activitiesList}>
|
<ul className="plain" ref={activitiesList}>
|
||||||
<li><p className={styles.tos}><button className='btn btn-link' onClick={() => openModal('About')}> <T path='agreement'/></button></p></li>
|
<li>
|
||||||
|
<p className={styles.tos}>
|
||||||
|
<button className="btn btn-link" onClick={() => openModal('About')}>
|
||||||
|
{' '}
|
||||||
|
<T path="agreement" />
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
{activities.map((activity, index) => (
|
{activities.map((activity, index) => (
|
||||||
<li key={index} className={`activity-item ${activity.type}`}>
|
<li key={index} className={`activity-item ${activity.type}`}>
|
||||||
<Activity activity={activity} scrollToBottom={scrollToBottomIfShould} />
|
<Activity activity={activity} scrollToBottom={scrollToBottomIfShould} />
|
||||||
@ -79,12 +86,12 @@ const ActivityList = ({ activities, openModal }) => {
|
|||||||
<ChatInput scrollToBottom={scrollToBottom} focusChat={focusChat} />
|
<ChatInput scrollToBottom={scrollToBottom} focusChat={focusChat} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
ActivityList.propTypes = {
|
ActivityList.propTypes = {
|
||||||
activities: PropTypes.array.isRequired,
|
activities: PropTypes.array.isRequired,
|
||||||
openModal: PropTypes.func.isRequired,
|
openModal: PropTypes.func.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ActivityList
|
export default ActivityList;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, fireEvent } from '@testing-library/react';
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
import ActivityList from './ActivityList';
|
import ActivityList from './ActivityList';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import configureStore from 'store';
|
import configureStore from 'store';
|
||||||
|
@ -36,7 +36,6 @@ jest.mock('utils/crypto', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Home component is displaying', async () => {
|
test('Home component is displaying', async () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
@ -18,7 +18,7 @@ const mapStateToProps = state => {
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
toggleNotificationAllowed,
|
toggleNotificationAllowed,
|
||||||
toggleNotificationEnabled
|
toggleNotificationEnabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
const WithNewMessageNotification = WrappedComponent => {
|
const WithNewMessageNotification = WrappedComponent => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Home from './Home'
|
import Home from './Home';
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
receiveEncryptedMessage,
|
receiveEncryptedMessage,
|
||||||
createUser,
|
createUser,
|
||||||
@ -13,12 +13,12 @@ import {
|
|||||||
receiveUnencryptedMessage,
|
receiveUnencryptedMessage,
|
||||||
sendUnencryptedMessage,
|
sendUnencryptedMessage,
|
||||||
sendEncryptedMessage,
|
sendEncryptedMessage,
|
||||||
setLanguage
|
setLanguage,
|
||||||
} from 'actions'
|
} from 'actions';
|
||||||
import WithNewMessageNotification from './WithNewMessageNotification'
|
import WithNewMessageNotification from './WithNewMessageNotification';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = state => {
|
||||||
const me = state.room.members.find(m => m.id === state.user.id)
|
const me = state.room.members.find(m => m.id === state.user.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activities: state.activities.items,
|
activities: state.activities.items,
|
||||||
@ -38,8 +38,8 @@ const mapStateToProps = (state) => {
|
|||||||
socketConnected: state.app.socketConnected,
|
socketConnected: state.app.socketConnected,
|
||||||
language: state.app.language,
|
language: state.app.language,
|
||||||
translations: state.app.translations,
|
translations: state.app.translations,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
receiveEncryptedMessage,
|
receiveEncryptedMessage,
|
||||||
@ -54,10 +54,7 @@ const mapDispatchToProps = {
|
|||||||
receiveUnencryptedMessage,
|
receiveUnencryptedMessage,
|
||||||
sendUnencryptedMessage,
|
sendUnencryptedMessage,
|
||||||
sendEncryptedMessage,
|
sendEncryptedMessage,
|
||||||
setLanguage
|
setLanguage,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default WithNewMessageNotification(connect(
|
export default WithNewMessageNotification(connect(mapStateToProps, mapDispatchToProps)(Home));
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Home))
|
|
||||||
|
@ -87,15 +87,15 @@ jest.mock('tinycon', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Connected Home component', () => {
|
describe('Connected Home component', () => {
|
||||||
beforeEach(()=>{
|
beforeEach(() => {
|
||||||
global.Notification = {
|
global.Notification = {
|
||||||
permission: 'granted'
|
permission: 'granted',
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(()=>{
|
afterEach(() => {
|
||||||
delete global.Notification
|
delete global.Notification;
|
||||||
})
|
});
|
||||||
|
|
||||||
it('should display', () => {
|
it('should display', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
@ -118,8 +118,8 @@ describe('Connected Home component', () => {
|
|||||||
expect(store.getState().app.notificationIsEnabled).toBe(true);
|
expect(store.getState().app.notificationIsEnabled).toBe(true);
|
||||||
|
|
||||||
global.Notification = {
|
global.Notification = {
|
||||||
permission: 'denied'
|
permission: 'denied',
|
||||||
}
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@ -130,8 +130,8 @@ describe('Connected Home component', () => {
|
|||||||
expect(store.getState().app.notificationIsAllowed).toBe(false);
|
expect(store.getState().app.notificationIsAllowed).toBe(false);
|
||||||
|
|
||||||
global.Notification = {
|
global.Notification = {
|
||||||
permission: 'default'
|
permission: 'default',
|
||||||
}
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@ -140,8 +140,6 @@ describe('Connected Home component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(store.getState().app.notificationIsAllowed).toBe(null);
|
expect(store.getState().app.notificationIsAllowed).toBe(null);
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send notifications', async () => {
|
it('should send notifications', async () => {
|
||||||
|
@ -3,13 +3,7 @@ import { render } from '@testing-library/react';
|
|||||||
import Message from '.';
|
import Message from '.';
|
||||||
|
|
||||||
test('Message component is displaying', async () => {
|
test('Message component is displaying', async () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(<Message sender={'linus'} timestamp={1588794269074} message={'we come in peace'} />);
|
||||||
<Message
|
|
||||||
sender={'linus'}
|
|
||||||
timestamp={1588794269074}
|
|
||||||
message={'we come in peace'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -1,29 +1,31 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import Username from 'components/Username'
|
import Username from 'components/Username';
|
||||||
import moment from 'moment'
|
import moment from 'moment';
|
||||||
import Linkify from 'react-linkify'
|
import Linkify from 'react-linkify';
|
||||||
|
|
||||||
class Message extends Component {
|
class Message extends Component {
|
||||||
render() {
|
render() {
|
||||||
const msg = decodeURI(this.props.message)
|
const msg = decodeURI(this.props.message);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="chat-meta">
|
<div className="chat-meta">
|
||||||
<Username username={this.props.sender} />
|
<Username username={this.props.sender} />
|
||||||
<span className="muted timestamp">
|
<span className="muted timestamp">{moment(this.props.timestamp).format('LT')}</span>
|
||||||
{moment(this.props.timestamp).format('LT')}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="chat">
|
<div className="chat">
|
||||||
<Linkify properties={{
|
<Linkify
|
||||||
target: '_blank',
|
properties={{
|
||||||
rel: 'noopener noreferrer',
|
target: '_blank',
|
||||||
}}>{msg}</Linkify>
|
rel: 'noopener noreferrer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{msg}
|
||||||
|
</Linkify>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +33,6 @@ Message.propTypes = {
|
|||||||
sender: PropTypes.string.isRequired,
|
sender: PropTypes.string.isRequired,
|
||||||
timestamp: PropTypes.number.isRequired,
|
timestamp: PropTypes.number.isRequired,
|
||||||
message: PropTypes.string.isRequired,
|
message: PropTypes.string.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Message
|
export default Message;
|
||||||
|
@ -6,7 +6,7 @@ test('Notice component is displaying', async () => {
|
|||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Notice level={'warning'}>
|
<Notice level={'warning'}>
|
||||||
<div>Hello world</div>
|
<div>Hello world</div>
|
||||||
</Notice>
|
</Notice>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const Notice = props => (
|
const Notice = props => (
|
||||||
<div>
|
<div>
|
||||||
<div className={classNames({
|
<div
|
||||||
info: props.level === 'info',
|
className={classNames({
|
||||||
warning: props.level === 'warning',
|
info: props.level === 'info',
|
||||||
danger: props.level === 'danger',
|
warning: props.level === 'warning',
|
||||||
})}>
|
danger: props.level === 'danger',
|
||||||
|
})}
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
|
||||||
Notice.defaultProps = {
|
Notice.defaultProps = {
|
||||||
level: 'info',
|
level: 'info',
|
||||||
}
|
};
|
||||||
|
|
||||||
Notice.propTypes = {
|
Notice.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
level: PropTypes.string,
|
level: PropTypes.string,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Notice
|
export default Notice;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
.info {
|
.info {
|
||||||
color: white
|
color: white;
|
||||||
}
|
}
|
||||||
.warning {
|
.warning {
|
||||||
color: yellow
|
color: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger {
|
.danger {
|
||||||
color: gold
|
color: gold;
|
||||||
}
|
}
|
||||||
|
@ -36,21 +36,15 @@ class RoomLink extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
<div className='form-group'>
|
<div className="form-group">
|
||||||
<div className='input-group'>
|
<div className="input-group">
|
||||||
<input
|
<input id="room-url" className="form-control" type="text" readOnly value={this.state.roomUrl} />
|
||||||
id='room-url'
|
<div className="input-group-append">
|
||||||
className='form-control'
|
|
||||||
type='text'
|
|
||||||
readOnly
|
|
||||||
value={this.state.roomUrl}
|
|
||||||
/>
|
|
||||||
<div className='input-group-append'>
|
|
||||||
<button
|
<button
|
||||||
className='copy-room btn btn-secondary'
|
className="copy-room btn btn-secondary"
|
||||||
type='button'
|
type="button"
|
||||||
data-toggle='tooltip'
|
data-toggle="tooltip"
|
||||||
data-placement='bottom'
|
data-placement="bottom"
|
||||||
data-clipboard-text={this.state.roomUrl}
|
data-clipboard-text={this.state.roomUrl}
|
||||||
title={this.props.translations.copyButtonTooltip}
|
title={this.props.translations.copyButtonTooltip}
|
||||||
>
|
>
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
export default class RoomLocked extends Component {
|
export default class RoomLocked extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return <div>{this.props.modalContent}</div>;
|
||||||
<div>
|
|
||||||
{this.props.modalContent}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,6 @@ describe('Settings component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should toggle sound', async () => {
|
it('should toggle sound', async () => {
|
||||||
@ -106,8 +105,7 @@ describe('Settings component', () => {
|
|||||||
|
|
||||||
delete global.Notification;
|
delete global.Notification;
|
||||||
|
|
||||||
waitFor(() =>expect(toggleNotifications).toHaveBeenCalledWith(false));
|
waitFor(() => expect(toggleNotifications).toHaveBeenCalledWith(false));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not toggle notifications', async () => {
|
it('should not toggle notifications', async () => {
|
||||||
@ -139,11 +137,10 @@ describe('Settings component', () => {
|
|||||||
|
|
||||||
delete global.Notification;
|
delete global.Notification;
|
||||||
|
|
||||||
waitFor(() =>expect(toggleAllowed).toHaveBeenCalledWith(false));
|
waitFor(() => expect(toggleAllowed).toHaveBeenCalledWith(false));
|
||||||
waitFor(() =>expect(toggleNotifications).not.toHaveBeenCalled());
|
waitFor(() => expect(toggleNotifications).not.toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should change lang', async () => {
|
it('should change lang', async () => {
|
||||||
const changeLang = jest.fn();
|
const changeLang = jest.fn();
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class Settings extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<label className="form-check-label" htmlFor="notif-control">
|
<label className="form-check-label" htmlFor="notif-control">
|
||||||
{this.props.notificationIsAllowed !== false &&
|
{this.props.notificationIsAllowed !== false && (
|
||||||
<>
|
<>
|
||||||
<input
|
<input
|
||||||
id="notif-control"
|
id="notif-control"
|
||||||
@ -62,7 +62,7 @@ class Settings extends Component {
|
|||||||
/>
|
/>
|
||||||
<T path="desktopNotification" />
|
<T path="desktopNotification" />
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
{this.props.notificationIsAllowed === false && <T path="desktopNotificationBlocked" />}
|
{this.props.notificationIsAllowed === false && <T path="desktopNotificationBlocked" />}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,11 +9,10 @@ class T extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const t = getTranslations(this.props.language);
|
const t = getTranslations(this.props.language);
|
||||||
const englishT = getTranslations('en');
|
const englishT = getTranslations('en');
|
||||||
const str =
|
const str = _.get(t, this.props.path, '') || _.get(englishT, this.props.path, '');
|
||||||
_.get(t, this.props.path, '') || _.get(englishT, this.props.path, '');
|
|
||||||
let string = str.split(regex);
|
let string = str.split(regex);
|
||||||
if (this.props.data) {
|
if (this.props.data) {
|
||||||
string = string.map((word) => {
|
string = string.map(word => {
|
||||||
if (this.props.data[word]) {
|
if (this.props.data[word]) {
|
||||||
return this.props.data[word];
|
return this.props.data[word];
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { render } from '@testing-library/react';
|
|||||||
import Username from '.';
|
import Username from '.';
|
||||||
|
|
||||||
test('Username component is displaying', async () => {
|
test('Username component is displaying', async () => {
|
||||||
const { asFragment } = render(<Username username='paul' />);
|
const { asFragment } = render(<Username username="paul" />);
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import randomColor from 'randomcolor'
|
import randomColor from 'randomcolor';
|
||||||
|
|
||||||
class Username extends Component {
|
class Username extends Component {
|
||||||
render() {
|
render() {
|
||||||
@ -8,12 +8,12 @@ class Username extends Component {
|
|||||||
<span className="username" style={{ color: randomColor({ seed: this.props.username, luminosity: 'light' }) }}>
|
<span className="username" style={{ color: randomColor({ seed: this.props.username, luminosity: 'light' }) }}>
|
||||||
{this.props.username}
|
{this.props.username}
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Username.propTypes = {
|
Username.propTypes = {
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Username
|
export default Username;
|
||||||
|
@ -4,9 +4,7 @@ import { render } from '@testing-library/react';
|
|||||||
import Welcome from '.';
|
import Welcome from '.';
|
||||||
|
|
||||||
test('Welcome component is displaying', async () => {
|
test('Welcome component is displaying', async () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(<Welcome roomId="roomtest" close={() => {}} translations={{}} />);
|
||||||
<Welcome roomId='roomtest' close={() => {}} translations={{}} />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
import RoomLink from 'components/RoomLink'
|
import RoomLink from 'components/RoomLink';
|
||||||
|
|
||||||
class Welcome extends Component {
|
class Welcome extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
roomUrl: `https://darkwire.io/${props.roomId}`,
|
roomUrl: `https://darkwire.io/${props.roomId}`,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -23,17 +23,23 @@ class Welcome extends Component {
|
|||||||
<li>Send files up to 4 MB</li>
|
<li>Send files up to 4 MB</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
You can learn more <a href="https://github.com/darkwire/darkwire.io" target="_blank" rel="noopener noreferrer">here</a>.
|
You can learn more{' '}
|
||||||
|
<a href="https://github.com/darkwire/darkwire.io" target="_blank" rel="noopener noreferrer">
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<p className='mb-2'>Others can join this room using the following URL:</p>
|
<p className="mb-2">Others can join this room using the following URL:</p>
|
||||||
<RoomLink roomId={this.props.roomId} translations={this.props.translations} />
|
<RoomLink roomId={this.props.roomId} translations={this.props.translations} />
|
||||||
<div className="react-modal-footer">
|
<div className="react-modal-footer">
|
||||||
<button className="btn btn-primary btn-lg" onClick={this.props.close}>{this.props.translations.welcomeModalCTA}</button>
|
<button className="btn btn-primary btn-lg" onClick={this.props.close}>
|
||||||
|
{this.props.translations.welcomeModalCTA}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +47,6 @@ Welcome.propTypes = {
|
|||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
close: PropTypes.func.isRequired,
|
close: PropTypes.func.isRequired,
|
||||||
translations: PropTypes.object.isRequired,
|
translations: PropTypes.object.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Welcome
|
export default Welcome;
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
export default process.env.NODE_ENV
|
export default process.env.NODE_ENV;
|
||||||
|
@ -18,7 +18,12 @@
|
|||||||
"settings": "die Einstellungen",
|
"settings": "die Einstellungen",
|
||||||
"settingsButton": "die Einstellungen",
|
"settingsButton": "die Einstellungen",
|
||||||
"settingsHeader": "Einstellungen & Hilfe",
|
"settingsHeader": "Einstellungen & Hilfe",
|
||||||
"slashCommandsBullets": ["Ändert den Benutzernamen", "führt eine Aktion aus", "löscht den Nachrichtenverlauf", "listet alle Befehle auf"],
|
"slashCommandsBullets": [
|
||||||
|
"Ändert den Benutzernamen",
|
||||||
|
"führt eine Aktion aus",
|
||||||
|
"löscht den Nachrichtenverlauf",
|
||||||
|
"listet alle Befehle auf"
|
||||||
|
],
|
||||||
"slashCommandsHeader": "Slash-Befehle",
|
"slashCommandsHeader": "Slash-Befehle",
|
||||||
"slashCommandsText": "Die folgenden Schrägstrichbefehle sind verfügbar:",
|
"slashCommandsText": "Die folgenden Schrägstrichbefehle sind verfügbar:",
|
||||||
"sound": "Klingen",
|
"sound": "Klingen",
|
||||||
|
@ -25,7 +25,12 @@
|
|||||||
"lockRoomText": "If you are the room owner, you can lock and unlock the room by clicking the lock icon in the nav bar. When a room is locked, no other participants will be able to join.",
|
"lockRoomText": "If you are the room owner, you can lock and unlock the room by clicking the lock icon in the nav bar. When a room is locked, no other participants will be able to join.",
|
||||||
"slashCommandsHeader": "Slash Commands",
|
"slashCommandsHeader": "Slash Commands",
|
||||||
"slashCommandsText": "The following slash commands are available:",
|
"slashCommandsText": "The following slash commands are available:",
|
||||||
"slashCommandsBullets": ["changes username", "performs an action", "clears your message history", "lists all commands"],
|
"slashCommandsBullets": [
|
||||||
|
"changes username",
|
||||||
|
"performs an action",
|
||||||
|
"clears your message history",
|
||||||
|
"lists all commands"
|
||||||
|
],
|
||||||
"sound": "Sound",
|
"sound": "Sound",
|
||||||
"newMessageNotification": "New message notification",
|
"newMessageNotification": "New message notification",
|
||||||
"desktopNotification": "Desktop Notification",
|
"desktopNotification": "Desktop Notification",
|
||||||
|
@ -25,7 +25,12 @@
|
|||||||
"lockRoomText": "Si vous êtes le propriétaire du salon, vous pouvez le verrouiller et le déverrouiller en cliquant sur l'icône de cadenas située dans la barre de navigation. Quand un salon est verrouillé, aucun autre participant ne peut rejoindre.",
|
"lockRoomText": "Si vous êtes le propriétaire du salon, vous pouvez le verrouiller et le déverrouiller en cliquant sur l'icône de cadenas située dans la barre de navigation. Quand un salon est verrouillé, aucun autre participant ne peut rejoindre.",
|
||||||
"slashCommandsHeader": "Commandes",
|
"slashCommandsHeader": "Commandes",
|
||||||
"slashCommandsText": "Les commandes suivantes sont disponibles :",
|
"slashCommandsText": "Les commandes suivantes sont disponibles :",
|
||||||
"slashCommandsBullets": ["changer de pseudo", "effectuer une action", "effacer votre historique de messages", "lister toutes les commandes"],
|
"slashCommandsBullets": [
|
||||||
|
"changer de pseudo",
|
||||||
|
"effectuer une action",
|
||||||
|
"effacer votre historique de messages",
|
||||||
|
"lister toutes les commandes"
|
||||||
|
],
|
||||||
"sound": "Son",
|
"sound": "Son",
|
||||||
"newMessageNotification": "Notification lors d'un nouveau message",
|
"newMessageNotification": "Notification lors d'un nouveau message",
|
||||||
"desktopNotification": "Notification Système",
|
"desktopNotification": "Notification Système",
|
||||||
|
@ -13,15 +13,15 @@ const languagesMap = {
|
|||||||
de,
|
de,
|
||||||
it,
|
it,
|
||||||
zhCN,
|
zhCN,
|
||||||
nl
|
nl,
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return best match for lang and variant.
|
* Return best match for lang and variant.
|
||||||
* @param {string} language string from navigator configuration or cookie.
|
* @param {string} language string from navigator configuration or cookie.
|
||||||
* @returns the translation dict
|
* @returns the translation dict
|
||||||
*/
|
*/
|
||||||
export function getTranslations(language = "") {
|
export function getTranslations(language = '') {
|
||||||
const [lang, variant] = language.split('-');
|
const [lang, variant] = language.split('-');
|
||||||
|
|
||||||
if (languagesMap.hasOwnProperty(`${lang}${variant}`)) {
|
if (languagesMap.hasOwnProperty(`${lang}${variant}`)) {
|
||||||
|
@ -18,7 +18,12 @@
|
|||||||
"settings": "impostazioni",
|
"settings": "impostazioni",
|
||||||
"settingsButton": "impostazioni",
|
"settingsButton": "impostazioni",
|
||||||
"settingsHeader": "Impostazioni e aiuto",
|
"settingsHeader": "Impostazioni e aiuto",
|
||||||
"slashCommandsBullets": ["cambia nome utente", "esegue un'azione", "cancella la cronologia dei messaggi", "elenca tutti i comandi"],
|
"slashCommandsBullets": [
|
||||||
|
"cambia nome utente",
|
||||||
|
"esegue un'azione",
|
||||||
|
"cancella la cronologia dei messaggi",
|
||||||
|
"elenca tutti i comandi"
|
||||||
|
],
|
||||||
"slashCommandsHeader": "Comandi",
|
"slashCommandsHeader": "Comandi",
|
||||||
"slashCommandsText": "Sono disponibili i seguenti comandi:",
|
"slashCommandsText": "Sono disponibili i seguenti comandi:",
|
||||||
"sound": "Suono",
|
"sound": "Suono",
|
||||||
|
@ -18,7 +18,12 @@
|
|||||||
"settings": "instellingen",
|
"settings": "instellingen",
|
||||||
"settingsButton": "instellingen",
|
"settingsButton": "instellingen",
|
||||||
"settingsHeader": "Instellingen & Help",
|
"settingsHeader": "Instellingen & Help",
|
||||||
"slashCommandsBullets": ["wijzigt gebruikersnaam", "voert een actie uit", "wist uw berichtgeschiedenis", "geeft alle opdrachten weer"],
|
"slashCommandsBullets": [
|
||||||
|
"wijzigt gebruikersnaam",
|
||||||
|
"voert een actie uit",
|
||||||
|
"wist uw berichtgeschiedenis",
|
||||||
|
"geeft alle opdrachten weer"
|
||||||
|
],
|
||||||
"slashCommandsHeader": "Slash-opdrachten",
|
"slashCommandsHeader": "Slash-opdrachten",
|
||||||
"slashCommandsText": "De volgende slash-opdrachten zijn beschikbaar:",
|
"slashCommandsText": "De volgende slash-opdrachten zijn beschikbaar:",
|
||||||
"sound": "Geluid",
|
"sound": "Geluid",
|
||||||
|
@ -25,7 +25,12 @@
|
|||||||
"lockRoomText": "Se sètz lo proprietari de la sala, podètz clavar e desclavar en clicar l’icòna del cadenat de la barra de navigacion. Quand una sala es clavada, cap de participant pòt pas la rejónher .",
|
"lockRoomText": "Se sètz lo proprietari de la sala, podètz clavar e desclavar en clicar l’icòna del cadenat de la barra de navigacion. Quand una sala es clavada, cap de participant pòt pas la rejónher .",
|
||||||
"slashCommandsHeader": "Comandas",
|
"slashCommandsHeader": "Comandas",
|
||||||
"slashCommandsText": "Las comandas seguentas son disponiblas :",
|
"slashCommandsText": "Las comandas seguentas son disponiblas :",
|
||||||
"slashCommandsBullets": ["càmbia d’escais-nom", "realiza una accion", "escafa l’istoric de conversacion", "lista totas las comandas"],
|
"slashCommandsBullets": [
|
||||||
|
"càmbia d’escais-nom",
|
||||||
|
"realiza una accion",
|
||||||
|
"escafa l’istoric de conversacion",
|
||||||
|
"lista totas las comandas"
|
||||||
|
],
|
||||||
"sound": "Son",
|
"sound": "Son",
|
||||||
"newMessageNotification": "New message notification",
|
"newMessageNotification": "New message notification",
|
||||||
"desktopNotification": "Desktop Notification",
|
"desktopNotification": "Desktop Notification",
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
|
||||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
monospace;
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
items: [],
|
items: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
const activities = (state = initialState, action) => {
|
const activities = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -8,7 +8,7 @@ const activities = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
items: [],
|
items: [],
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_SLASH_COMMAND':
|
case 'SEND_ENCRYPTED_MESSAGE_SLASH_COMMAND':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -19,7 +19,7 @@ const activities = (state = initialState, action) => {
|
|||||||
type: 'SLASH_COMMAND',
|
type: 'SLASH_COMMAND',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_FILE_TRANSFER':
|
case 'SEND_ENCRYPTED_MESSAGE_FILE_TRANSFER':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -30,7 +30,7 @@ const activities = (state = initialState, action) => {
|
|||||||
type: 'FILE',
|
type: 'FILE',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_TEXT_MESSAGE':
|
case 'SEND_ENCRYPTED_MESSAGE_TEXT_MESSAGE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -41,7 +41,7 @@ const activities = (state = initialState, action) => {
|
|||||||
type: 'TEXT_MESSAGE',
|
type: 'TEXT_MESSAGE',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE':
|
case 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -52,7 +52,7 @@ const activities = (state = initialState, action) => {
|
|||||||
type: 'TEXT_MESSAGE',
|
type: 'TEXT_MESSAGE',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_SEND_FILE':
|
case 'SEND_ENCRYPTED_MESSAGE_SEND_FILE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -63,7 +63,7 @@ const activities = (state = initialState, action) => {
|
|||||||
type: 'SEND_FILE',
|
type: 'SEND_FILE',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'RECEIVE_ENCRYPTED_MESSAGE_SEND_FILE':
|
case 'RECEIVE_ENCRYPTED_MESSAGE_SEND_FILE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -74,13 +74,13 @@ const activities = (state = initialState, action) => {
|
|||||||
type: 'RECEIVE_FILE',
|
type: 'RECEIVE_FILE',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER':
|
case 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER':
|
||||||
const newUserId = action.payload.payload.id
|
const newUserId = action.payload.payload.id;
|
||||||
|
|
||||||
const haveUser = action.payload.state.room.members.find(m => m.id === newUserId)
|
const haveUser = action.payload.state.room.members.find(m => m.id === newUserId);
|
||||||
if (haveUser) {
|
if (haveUser) {
|
||||||
return state
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -93,10 +93,10 @@ const activities = (state = initialState, action) => {
|
|||||||
username: action.payload.payload.username,
|
username: action.payload.payload.username,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'USER_EXIT':
|
case 'USER_EXIT':
|
||||||
if (!action.payload.id) {
|
if (!action.payload.id) {
|
||||||
return state
|
return state;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -108,7 +108,7 @@ const activities = (state = initialState, action) => {
|
|||||||
username: action.payload.username,
|
username: action.payload.username,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'TOGGLE_LOCK_ROOM':
|
case 'TOGGLE_LOCK_ROOM':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -122,7 +122,7 @@ const activities = (state = initialState, action) => {
|
|||||||
sender: action.payload.sender,
|
sender: action.payload.sender,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'RECEIVE_TOGGLE_LOCK_ROOM':
|
case 'RECEIVE_TOGGLE_LOCK_ROOM':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -136,7 +136,7 @@ const activities = (state = initialState, action) => {
|
|||||||
sender: action.payload.sender,
|
sender: action.payload.sender,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'SHOW_NOTICE':
|
case 'SHOW_NOTICE':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -147,7 +147,7 @@ const activities = (state = initialState, action) => {
|
|||||||
message: action.payload.message,
|
message: action.payload.message,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -158,16 +158,16 @@ const activities = (state = initialState, action) => {
|
|||||||
currentUsername: action.payload.currentUsername,
|
currentUsername: action.payload.currentUsername,
|
||||||
newUsername: action.payload.newUsername,
|
newUsername: action.payload.newUsername,
|
||||||
},
|
},
|
||||||
].map((item) => {
|
].map(item => {
|
||||||
if (item.sender === action.payload.sender && item.type !== 'CHANGE_USERNAME') {
|
if (item.sender === action.payload.sender && item.type !== 'CHANGE_USERNAME') {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
username: action.payload.newUsername,
|
username: action.payload.newUsername,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return item
|
return item;
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
case 'RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
case 'RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -178,16 +178,16 @@ const activities = (state = initialState, action) => {
|
|||||||
currentUsername: action.payload.payload.currentUsername,
|
currentUsername: action.payload.payload.currentUsername,
|
||||||
newUsername: action.payload.payload.newUsername,
|
newUsername: action.payload.payload.newUsername,
|
||||||
},
|
},
|
||||||
].map((item) => {
|
].map(item => {
|
||||||
if (['TEXT_MESSAGE', 'USER_ACTION'].includes(item.type) && item.sender === action.payload.payload.sender) {
|
if (['TEXT_MESSAGE', 'USER_ACTION'].includes(item.type) && item.sender === action.payload.payload.sender) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
username: action.payload.payload.newUsername,
|
username: action.payload.payload.newUsername,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return item
|
return item;
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_USER_ACTION':
|
case 'SEND_ENCRYPTED_MESSAGE_USER_ACTION':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -198,7 +198,7 @@ const activities = (state = initialState, action) => {
|
|||||||
...action.payload,
|
...action.payload,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'RECEIVE_ENCRYPTED_MESSAGE_USER_ACTION':
|
case 'RECEIVE_ENCRYPTED_MESSAGE_USER_ACTION':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -209,10 +209,10 @@ const activities = (state = initialState, action) => {
|
|||||||
...action.payload.payload,
|
...action.payload.payload,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
default:
|
default:
|
||||||
return state
|
return state;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default activities
|
export default activities;
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
import { combineReducers } from 'redux'
|
import { combineReducers } from 'redux';
|
||||||
import app from './app'
|
import app from './app';
|
||||||
import activities from './activities'
|
import activities from './activities';
|
||||||
import user from './user'
|
import user from './user';
|
||||||
import room from './room'
|
import room from './room';
|
||||||
|
|
||||||
const appReducer = combineReducers({
|
const appReducer = combineReducers({
|
||||||
app,
|
app,
|
||||||
user,
|
user,
|
||||||
room,
|
room,
|
||||||
activities,
|
activities,
|
||||||
})
|
});
|
||||||
|
|
||||||
const rootReducer = (state, action) => appReducer(state, action)
|
const rootReducer = (state, action) => appReducer(state, action);
|
||||||
|
|
||||||
export default rootReducer
|
export default rootReducer;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
members: [
|
members: [
|
||||||
@ -9,42 +9,42 @@ const initialState = {
|
|||||||
],
|
],
|
||||||
id: '',
|
id: '',
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
const room = (state = initialState, action) => {
|
const room = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'USER_EXIT':
|
case 'USER_EXIT':
|
||||||
const memberPubKeys = action.payload.members.map(m => m.publicKey.n)
|
const memberPubKeys = action.payload.members.map(m => m.publicKey.n);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
members: state.members
|
members: state.members
|
||||||
.filter(member => memberPubKeys.includes(member.publicKey.n))
|
.filter(member => memberPubKeys.includes(member.publicKey.n))
|
||||||
.map(member => {
|
.map(member => {
|
||||||
const thisMember = action.payload.members.find(mem => mem.publicKey.n === member.id)
|
const thisMember = action.payload.members.find(mem => mem.publicKey.n === member.id);
|
||||||
if (thisMember){
|
if (thisMember) {
|
||||||
return {
|
return {
|
||||||
...member,
|
...member,
|
||||||
isOwner: thisMember.isOwner
|
isOwner: thisMember.isOwner,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return {...member}
|
return { ...member };
|
||||||
})
|
}),
|
||||||
}
|
};
|
||||||
case 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER':
|
case 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
members: state.members.map((member) => {
|
members: state.members.map(member => {
|
||||||
if (member.publicKey.n === action.payload.payload.publicKey.n) {
|
if (member.publicKey.n === action.payload.payload.publicKey.n) {
|
||||||
return {
|
return {
|
||||||
...member,
|
...member,
|
||||||
username: action.payload.payload.username,
|
username: action.payload.payload.username,
|
||||||
isOwner: action.payload.payload.isOwner,
|
isOwner: action.payload.payload.isOwner,
|
||||||
id: action.payload.payload.publicKey.n,
|
id: action.payload.payload.publicKey.n,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return member
|
return member;
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
case 'CREATE_USER':
|
case 'CREATE_USER':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -56,7 +56,7 @@ const room = (state = initialState, action) => {
|
|||||||
id: action.payload.publicKey.n,
|
id: action.payload.publicKey.n,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
case 'USER_ENTER':
|
case 'USER_ENTER':
|
||||||
const members = _.uniqBy(action.payload.users, member => member.publicKey.n);
|
const members = _.uniqBy(action.payload.users, member => member.publicKey.n);
|
||||||
|
|
||||||
@ -65,15 +65,15 @@ const room = (state = initialState, action) => {
|
|||||||
id: action.payload.id,
|
id: action.payload.id,
|
||||||
isLocked: Boolean(action.payload.isLocked),
|
isLocked: Boolean(action.payload.isLocked),
|
||||||
members: members.reduce((acc, user) => {
|
members: members.reduce((acc, user) => {
|
||||||
const exists = state.members.find(m => m.publicKey.n === user.publicKey.n)
|
const exists = state.members.find(m => m.publicKey.n === user.publicKey.n);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return [
|
return [
|
||||||
...acc,
|
...acc,
|
||||||
{
|
{
|
||||||
...user,
|
...user,
|
||||||
...exists,
|
...exists,
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
...acc,
|
...acc,
|
||||||
@ -81,49 +81,51 @@ const room = (state = initialState, action) => {
|
|||||||
publicKey: user.publicKey,
|
publicKey: user.publicKey,
|
||||||
isOwner: user.isOwner,
|
isOwner: user.isOwner,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}, []),
|
}, []),
|
||||||
}
|
};
|
||||||
case 'TOGGLE_LOCK_ROOM':
|
case 'TOGGLE_LOCK_ROOM':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isLocked: !state.isLocked,
|
isLocked: !state.isLocked,
|
||||||
}
|
};
|
||||||
case 'RECEIVE_TOGGLE_LOCK_ROOM':
|
case 'RECEIVE_TOGGLE_LOCK_ROOM':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isLocked: action.payload.locked,
|
isLocked: action.payload.locked,
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
||||||
const newUsername = action.payload.newUsername
|
const newUsername = action.payload.newUsername;
|
||||||
const userId = action.payload.id
|
const userId = action.payload.id;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
members: state.members.map(member => (
|
members: state.members.map(member =>
|
||||||
member.id === userId ?
|
member.id === userId
|
||||||
{
|
? {
|
||||||
...member,
|
...member,
|
||||||
username: newUsername,
|
username: newUsername,
|
||||||
} : member
|
}
|
||||||
)),
|
: member,
|
||||||
}
|
),
|
||||||
|
};
|
||||||
case 'RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
case 'RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
||||||
const newUsername2 = action.payload.payload.newUsername
|
const newUsername2 = action.payload.payload.newUsername;
|
||||||
const userId2 = action.payload.payload.id
|
const userId2 = action.payload.payload.id;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
members: state.members.map(member => (
|
members: state.members.map(member =>
|
||||||
member.id === userId2 ?
|
member.id === userId2
|
||||||
{
|
? {
|
||||||
...member,
|
...member,
|
||||||
username: newUsername2,
|
username: newUsername2,
|
||||||
} : member
|
}
|
||||||
)),
|
: member,
|
||||||
}
|
),
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state
|
return state;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default room
|
export default room;
|
||||||
|
@ -3,7 +3,7 @@ const initialState = {
|
|||||||
publicKey: {},
|
publicKey: {},
|
||||||
username: '',
|
username: '',
|
||||||
id: '',
|
id: '',
|
||||||
}
|
};
|
||||||
|
|
||||||
const user = (state = initialState, action) => {
|
const user = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -11,15 +11,15 @@ const user = (state = initialState, action) => {
|
|||||||
return {
|
return {
|
||||||
...action.payload,
|
...action.payload,
|
||||||
id: action.payload.publicKey.n,
|
id: action.payload.publicKey.n,
|
||||||
}
|
};
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
username: action.payload.newUsername,
|
username: action.payload.newUsername,
|
||||||
}
|
};
|
||||||
default:
|
default:
|
||||||
return state
|
return state;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default user
|
export default user;
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import 'react-simple-dropdown/styles/Dropdown.css'
|
import 'react-simple-dropdown/styles/Dropdown.css';
|
||||||
import 'stylesheets/app.sass'
|
import 'stylesheets/app.sass';
|
||||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react';
|
||||||
import { Redirect } from 'react-router'
|
import { Redirect } from 'react-router';
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux';
|
||||||
import configureStore from 'store'
|
import configureStore from 'store';
|
||||||
import { BrowserRouter, Route, Switch } from 'react-router-dom'
|
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||||
import shortId from 'shortid'
|
import shortId from 'shortid';
|
||||||
import Home from 'components/Home'
|
import Home from 'components/Home';
|
||||||
import { hasTouchSupport } from './utils/dom'
|
import { hasTouchSupport } from './utils/dom';
|
||||||
|
|
||||||
const store = configureStore()
|
const store = configureStore();
|
||||||
|
|
||||||
export default class Root extends Component {
|
export default class Root extends Component {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
if (hasTouchSupport) {
|
if (hasTouchSupport) {
|
||||||
document.body.classList.add('touch')
|
document.body.classList.add('touch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +32,6 @@ export default class Root extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,7 @@ const isLocalhost = Boolean(
|
|||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config) {
|
||||||
@ -44,7 +42,7 @@ export function register(config) {
|
|||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
'This web app is being served cache-first by a service ' +
|
'This web app is being served cache-first by a service ' +
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
'worker. To learn more, visit https://bit.ly/CRA-PWA',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -72,7 +70,7 @@ function registerValidSW(swUrl, config) {
|
|||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log(
|
||||||
'New content is available and will be used when all ' +
|
'New content is available and will be used when all ' +
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
@ -105,10 +103,7 @@ function checkValidServiceWorker(swUrl, config) {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
const contentType = response.headers.get('content-type');
|
const contentType = response.headers.get('content-type');
|
||||||
if (
|
if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
|
||||||
response.status === 404 ||
|
|
||||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
|
||||||
) {
|
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
registration.unregister().then(() => {
|
registration.unregister().then(() => {
|
||||||
@ -121,9 +116,7 @@ function checkValidServiceWorker(swUrl, config) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log('No internet connection found. App is running in offline mode.');
|
||||||
'No internet connection found. App is running in offline mode.'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,28 +1,25 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
import { createStore, applyMiddleware, compose } from 'redux'
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import reducers from 'reducers'
|
import reducers from 'reducers';
|
||||||
import thunk from 'redux-thunk'
|
import thunk from 'redux-thunk';
|
||||||
|
|
||||||
const composeEnhancers = process.env.NODE_ENV === 'production' ? compose : (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose)
|
const composeEnhancers =
|
||||||
|
process.env.NODE_ENV === 'production' ? compose : window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
||||||
const enabledMiddlewares = [thunk]
|
const enabledMiddlewares = [thunk];
|
||||||
|
|
||||||
const middlewares = applyMiddleware(...enabledMiddlewares)
|
const middlewares = applyMiddleware(...enabledMiddlewares);
|
||||||
|
|
||||||
export default function configureStore(preloadedState) {
|
export default function configureStore(preloadedState) {
|
||||||
const store = createStore(
|
const store = createStore(reducers, preloadedState, composeEnhancers(middlewares));
|
||||||
reducers,
|
|
||||||
preloadedState,
|
|
||||||
composeEnhancers(middlewares)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept('../reducers', () => {
|
module.hot.accept('../reducers', () => {
|
||||||
// eslint-disable-next-line global-require
|
// eslint-disable-next-line global-require
|
||||||
const nextRootReducer = require('../reducers/index')
|
const nextRootReducer = require('../reducers/index');
|
||||||
store.replaceReducer(nextRootReducer)
|
store.replaceReducer(nextRootReducer);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store;
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,4 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
require('autoprefixer')({}), // eslint-disable-line
|
require('autoprefixer')({}), // eslint-disable-line
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
@ -1,250 +1,246 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
function Zoom(domContent) {
|
function Zoom(domContent) {
|
||||||
const OFFSET = 80
|
const OFFSET = 80;
|
||||||
|
|
||||||
// From http://youmightnotneedjquery.com/#offset
|
// From http://youmightnotneedjquery.com/#offset
|
||||||
function offset(element) {
|
function offset(element) {
|
||||||
const rect = element.getBoundingClientRect()
|
const rect = element.getBoundingClientRect();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
top: rect.top + document.body.scrollTop,
|
top: rect.top + document.body.scrollTop,
|
||||||
left: rect.left + document.body.scrollLeft,
|
left: rect.left + document.body.scrollLeft,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomListener() {
|
function zoomListener() {
|
||||||
let activeZoom = null
|
let activeZoom = null;
|
||||||
let initialScrollPosition = null
|
let initialScrollPosition = null;
|
||||||
let initialTouchPosition = null
|
let initialTouchPosition = null;
|
||||||
|
|
||||||
function listen() {
|
function listen() {
|
||||||
domContent.addEventListener('click', (event) => {
|
domContent.addEventListener('click', event => {
|
||||||
if (event.target.tagName !== 'IMG') return
|
if (event.target.tagName !== 'IMG') return;
|
||||||
|
|
||||||
zoom(event)
|
zoom(event);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoom(event) {
|
function zoom(event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
|
|
||||||
if (document.body.classList.contains('zoom-overlay-open')) return
|
if (document.body.classList.contains('zoom-overlay-open')) return;
|
||||||
if (event.target.width >= (window.innerWidth - OFFSET)) return
|
if (event.target.width >= window.innerWidth - OFFSET) return;
|
||||||
|
|
||||||
if (event.metaKey || event.ctrlKey) return openInNewWindow()
|
if (event.metaKey || event.ctrlKey) return openInNewWindow();
|
||||||
|
|
||||||
closeActiveZoom({ forceDispose: true })
|
closeActiveZoom({ forceDispose: true });
|
||||||
|
|
||||||
activeZoom = vanillaZoom(event.target)
|
activeZoom = vanillaZoom(event.target);
|
||||||
activeZoom.zoomImage()
|
activeZoom.zoomImage();
|
||||||
|
|
||||||
addCloseActiveZoomListeners()
|
addCloseActiveZoomListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openInNewWindow() {
|
function openInNewWindow() {
|
||||||
window.open(event.target.getAttribute('data-original') ||
|
window.open(event.target.getAttribute('data-original') || event.target.currentSrc || event.target.src, '_blank');
|
||||||
event.target.currentSrc ||
|
|
||||||
event.target.src,
|
|
||||||
'_blank')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeActiveZoom(options) {
|
function closeActiveZoom(options) {
|
||||||
options = options || { forceDispose: false }
|
options = options || { forceDispose: false };
|
||||||
if (!activeZoom) return
|
if (!activeZoom) return;
|
||||||
|
|
||||||
activeZoom[options.forceDispose ? 'dispose' : 'close']()
|
activeZoom[options.forceDispose ? 'dispose' : 'close']();
|
||||||
removeCloseActiveZoomListeners()
|
removeCloseActiveZoomListeners();
|
||||||
activeZoom = null
|
activeZoom = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCloseActiveZoomListeners() {
|
function addCloseActiveZoomListeners() {
|
||||||
// todo(fat): probably worth throttling this
|
// todo(fat): probably worth throttling this
|
||||||
window.addEventListener('scroll', handleScroll)
|
window.addEventListener('scroll', handleScroll);
|
||||||
document.addEventListener('click', handleClick)
|
document.addEventListener('click', handleClick);
|
||||||
document.addEventListener('keyup', handleEscPressed)
|
document.addEventListener('keyup', handleEscPressed);
|
||||||
document.addEventListener('touchstart', handleTouchStart)
|
document.addEventListener('touchstart', handleTouchStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCloseActiveZoomListeners() {
|
function removeCloseActiveZoomListeners() {
|
||||||
window.removeEventListener('scroll', handleScroll)
|
window.removeEventListener('scroll', handleScroll);
|
||||||
document.removeEventListener('keyup', handleEscPressed)
|
document.removeEventListener('keyup', handleEscPressed);
|
||||||
document.removeEventListener('click', handleClick)
|
document.removeEventListener('click', handleClick);
|
||||||
document.removeEventListener('touchstart', handleTouchStart)
|
document.removeEventListener('touchstart', handleTouchStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleScroll(event) {
|
function handleScroll(event) {
|
||||||
if (initialScrollPosition === null) initialScrollPosition = window.scrollY
|
if (initialScrollPosition === null) initialScrollPosition = window.scrollY;
|
||||||
const deltaY = initialScrollPosition - window.scrollY
|
const deltaY = initialScrollPosition - window.scrollY;
|
||||||
if (Math.abs(deltaY) >= 40) closeActiveZoom()
|
if (Math.abs(deltaY) >= 40) closeActiveZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEscPressed(event) {
|
function handleEscPressed(event) {
|
||||||
if (event.keyCode == 27) closeActiveZoom()
|
if (event.keyCode == 27) closeActiveZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick(event) {
|
function handleClick(event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
closeActiveZoom()
|
closeActiveZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTouchStart(event) {
|
function handleTouchStart(event) {
|
||||||
initialTouchPosition = event.touches[0].pageY
|
initialTouchPosition = event.touches[0].pageY;
|
||||||
event.target.addEventListener('touchmove', handleTouchMove)
|
event.target.addEventListener('touchmove', handleTouchMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTouchMove(event) {
|
function handleTouchMove(event) {
|
||||||
if (Math.abs(event.touches[0].pageY - initialTouchPosition) <= 10) return
|
if (Math.abs(event.touches[0].pageY - initialTouchPosition) <= 10) return;
|
||||||
closeActiveZoom()
|
closeActiveZoom();
|
||||||
event.target.removeEventListener('touchmove', handleTouchMove)
|
event.target.removeEventListener('touchmove', handleTouchMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { listen }
|
return { listen };
|
||||||
}
|
}
|
||||||
|
|
||||||
var vanillaZoom = (function () {
|
var vanillaZoom = (function () {
|
||||||
let fullHeight = null
|
let fullHeight = null;
|
||||||
let fullWidth = null
|
let fullWidth = null;
|
||||||
let overlay = null
|
let overlay = null;
|
||||||
let imgScaleFactor = null
|
let imgScaleFactor = null;
|
||||||
|
|
||||||
let targetImage = null
|
let targetImage = null;
|
||||||
let targetImageWrap = null
|
let targetImageWrap = null;
|
||||||
let targetImageClone = null
|
let targetImageClone = null;
|
||||||
|
|
||||||
function zoomImage() {
|
function zoomImage() {
|
||||||
const img = document.createElement('img')
|
const img = document.createElement('img');
|
||||||
img.onload = function () {
|
img.onload = function () {
|
||||||
fullHeight = Number(img.height)
|
fullHeight = Number(img.height);
|
||||||
fullWidth = Number(img.width)
|
fullWidth = Number(img.width);
|
||||||
zoomOriginal()
|
zoomOriginal();
|
||||||
}
|
};
|
||||||
img.src = targetImage.currentSrc || targetImage.src
|
img.src = targetImage.currentSrc || targetImage.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomOriginal() {
|
function zoomOriginal() {
|
||||||
targetImageWrap = document.createElement('div')
|
targetImageWrap = document.createElement('div');
|
||||||
targetImageWrap.className = 'zoom-img-wrap'
|
targetImageWrap.className = 'zoom-img-wrap';
|
||||||
targetImageWrap.style.position = 'absolute'
|
targetImageWrap.style.position = 'absolute';
|
||||||
targetImageWrap.style.top = `${offset(targetImage).top}px`
|
targetImageWrap.style.top = `${offset(targetImage).top}px`;
|
||||||
targetImageWrap.style.left = `${offset(targetImage).left}px`
|
targetImageWrap.style.left = `${offset(targetImage).left}px`;
|
||||||
|
|
||||||
targetImageClone = targetImage.cloneNode()
|
targetImageClone = targetImage.cloneNode();
|
||||||
targetImageClone.style.visibility = 'hidden'
|
targetImageClone.style.visibility = 'hidden';
|
||||||
|
|
||||||
targetImage.style.width = `${targetImage.offsetWidth}px`
|
targetImage.style.width = `${targetImage.offsetWidth}px`;
|
||||||
targetImage.parentNode.replaceChild(targetImageClone, targetImage)
|
targetImage.parentNode.replaceChild(targetImageClone, targetImage);
|
||||||
|
|
||||||
document.body.appendChild(targetImageWrap)
|
document.body.appendChild(targetImageWrap);
|
||||||
targetImageWrap.appendChild(targetImage)
|
targetImageWrap.appendChild(targetImage);
|
||||||
|
|
||||||
targetImage.classList.add('zoom-img')
|
targetImage.classList.add('zoom-img');
|
||||||
targetImage.setAttribute('data-action', 'zoom-out')
|
targetImage.setAttribute('data-action', 'zoom-out');
|
||||||
|
|
||||||
overlay = document.createElement('div')
|
overlay = document.createElement('div');
|
||||||
overlay.className = 'zoom-overlay'
|
overlay.className = 'zoom-overlay';
|
||||||
|
|
||||||
document.body.appendChild(overlay)
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
calculateZoom()
|
calculateZoom();
|
||||||
triggerAnimation()
|
triggerAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateZoom() {
|
function calculateZoom() {
|
||||||
targetImage.offsetWidth // repaint before animating
|
targetImage.offsetWidth; // repaint before animating
|
||||||
|
|
||||||
const originalFullImageWidth = fullWidth
|
const originalFullImageWidth = fullWidth;
|
||||||
const originalFullImageHeight = fullHeight
|
const originalFullImageHeight = fullHeight;
|
||||||
|
|
||||||
const maxScaleFactor = originalFullImageWidth / targetImage.width
|
const maxScaleFactor = originalFullImageWidth / targetImage.width;
|
||||||
|
|
||||||
const viewportHeight = window.innerHeight - OFFSET
|
const viewportHeight = window.innerHeight - OFFSET;
|
||||||
const viewportWidth = window.innerWidth - OFFSET
|
const viewportWidth = window.innerWidth - OFFSET;
|
||||||
|
|
||||||
const imageAspectRatio = originalFullImageWidth / originalFullImageHeight
|
const imageAspectRatio = originalFullImageWidth / originalFullImageHeight;
|
||||||
const viewportAspectRatio = viewportWidth / viewportHeight
|
const viewportAspectRatio = viewportWidth / viewportHeight;
|
||||||
|
|
||||||
if (originalFullImageWidth < viewportWidth && originalFullImageHeight < viewportHeight) {
|
if (originalFullImageWidth < viewportWidth && originalFullImageHeight < viewportHeight) {
|
||||||
imgScaleFactor = maxScaleFactor
|
imgScaleFactor = maxScaleFactor;
|
||||||
} else if (imageAspectRatio < viewportAspectRatio) {
|
} else if (imageAspectRatio < viewportAspectRatio) {
|
||||||
imgScaleFactor = (viewportHeight / originalFullImageHeight) * maxScaleFactor
|
imgScaleFactor = (viewportHeight / originalFullImageHeight) * maxScaleFactor;
|
||||||
} else {
|
} else {
|
||||||
imgScaleFactor = (viewportWidth / originalFullImageWidth) * maxScaleFactor
|
imgScaleFactor = (viewportWidth / originalFullImageWidth) * maxScaleFactor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerAnimation() {
|
function triggerAnimation() {
|
||||||
targetImage.offsetWidth // repaint before animating
|
targetImage.offsetWidth; // repaint before animating
|
||||||
|
|
||||||
const imageOffset = offset(targetImage)
|
const imageOffset = offset(targetImage);
|
||||||
const scrollTop = window.scrollY
|
const scrollTop = window.scrollY;
|
||||||
|
|
||||||
const viewportY = scrollTop + (window.innerHeight / 2)
|
const viewportY = scrollTop + window.innerHeight / 2;
|
||||||
const viewportX = (window.innerWidth / 2)
|
const viewportX = window.innerWidth / 2;
|
||||||
|
|
||||||
const imageCenterY = imageOffset.top + (targetImage.height / 2)
|
const imageCenterY = imageOffset.top + targetImage.height / 2;
|
||||||
const imageCenterX = imageOffset.left + (targetImage.width / 2)
|
const imageCenterX = imageOffset.left + targetImage.width / 2;
|
||||||
|
|
||||||
const translateY = viewportY - imageCenterY
|
const translateY = viewportY - imageCenterY;
|
||||||
const translateX = viewportX - imageCenterX
|
const translateX = viewportX - imageCenterX;
|
||||||
|
|
||||||
const targetImageTransform = `scale(${imgScaleFactor})`
|
const targetImageTransform = `scale(${imgScaleFactor})`;
|
||||||
const targetImageWrapTransform =
|
const targetImageWrapTransform = `translate(${translateX}px, ${translateY}px) translateZ(0)`;
|
||||||
`translate(${translateX}px, ${translateY}px) translateZ(0)`
|
|
||||||
|
|
||||||
targetImage.style.webkitTransform = targetImageTransform
|
targetImage.style.webkitTransform = targetImageTransform;
|
||||||
targetImage.style.msTransform = targetImageTransform
|
targetImage.style.msTransform = targetImageTransform;
|
||||||
targetImage.style.transform = targetImageTransform
|
targetImage.style.transform = targetImageTransform;
|
||||||
|
|
||||||
targetImageWrap.style.webkitTransform = targetImageWrapTransform
|
targetImageWrap.style.webkitTransform = targetImageWrapTransform;
|
||||||
targetImageWrap.style.msTransform = targetImageWrapTransform
|
targetImageWrap.style.msTransform = targetImageWrapTransform;
|
||||||
targetImageWrap.style.transform = targetImageWrapTransform
|
targetImageWrap.style.transform = targetImageWrapTransform;
|
||||||
|
|
||||||
document.body.classList.add('zoom-overlay-open')
|
document.body.classList.add('zoom-overlay-open');
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
document.body.classList.remove('zoom-overlay-open')
|
document.body.classList.remove('zoom-overlay-open');
|
||||||
document.body.classList.add('zoom-overlay-transitioning')
|
document.body.classList.add('zoom-overlay-transitioning');
|
||||||
|
|
||||||
targetImage.style.webkitTransform = ''
|
targetImage.style.webkitTransform = '';
|
||||||
targetImage.style.msTransform = ''
|
targetImage.style.msTransform = '';
|
||||||
targetImage.style.transform = ''
|
targetImage.style.transform = '';
|
||||||
|
|
||||||
targetImageWrap.style.webkitTransform = ''
|
targetImageWrap.style.webkitTransform = '';
|
||||||
targetImageWrap.style.msTransform = ''
|
targetImageWrap.style.msTransform = '';
|
||||||
targetImageWrap.style.transform = ''
|
targetImageWrap.style.transform = '';
|
||||||
|
|
||||||
if (!('transition' in document.body.style)) return dispose()
|
if (!('transition' in document.body.style)) return dispose();
|
||||||
|
|
||||||
targetImage.addEventListener('transitionend', dispose)
|
targetImage.addEventListener('transitionend', dispose);
|
||||||
targetImage.addEventListener('webkitTransitionEnd', dispose)
|
targetImage.addEventListener('webkitTransitionEnd', dispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispose() {
|
function dispose() {
|
||||||
targetImage.removeEventListener('transitionend', dispose)
|
targetImage.removeEventListener('transitionend', dispose);
|
||||||
targetImage.removeEventListener('webkitTransitionEnd', dispose)
|
targetImage.removeEventListener('webkitTransitionEnd', dispose);
|
||||||
|
|
||||||
if (!targetImageWrap || !targetImageWrap.parentNode) return
|
if (!targetImageWrap || !targetImageWrap.parentNode) return;
|
||||||
|
|
||||||
targetImage.classList.remove('zoom-img')
|
targetImage.classList.remove('zoom-img');
|
||||||
targetImage.style.width = ''
|
targetImage.style.width = '';
|
||||||
targetImage.setAttribute('data-action', 'zoom')
|
targetImage.setAttribute('data-action', 'zoom');
|
||||||
|
|
||||||
targetImageClone.parentNode.replaceChild(targetImage, targetImageClone)
|
targetImageClone.parentNode.replaceChild(targetImage, targetImageClone);
|
||||||
targetImageWrap.parentNode.removeChild(targetImageWrap)
|
targetImageWrap.parentNode.removeChild(targetImageWrap);
|
||||||
overlay.parentNode.removeChild(overlay)
|
overlay.parentNode.removeChild(overlay);
|
||||||
|
|
||||||
document.body.classList.remove('zoom-overlay-transitioning')
|
document.body.classList.remove('zoom-overlay-transitioning');
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (target) {
|
return function (target) {
|
||||||
targetImage = target
|
targetImage = target;
|
||||||
return { zoomImage, close, dispose }
|
return { zoomImage, close, dispose };
|
||||||
}
|
};
|
||||||
}())
|
})();
|
||||||
|
|
||||||
zoomListener().listen()
|
zoomListener().listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Zoom
|
export default Zoom;
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
export default class Crypto {
|
export default class Crypto {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._crypto = window.crypto || false
|
this._crypto = window.crypto || false;
|
||||||
|
|
||||||
if (!this._crypto || (!this._crypto.subtle && !this._crypto.webkitSubtle)) {
|
if (!this._crypto || (!this._crypto.subtle && !this._crypto.webkitSubtle)) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get crypto() {
|
get crypto() {
|
||||||
return this._crypto
|
return this._crypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
convertStringToArrayBufferView(str) {
|
convertStringToArrayBufferView(str) {
|
||||||
const bytes = new Uint8Array(str.length)
|
const bytes = new Uint8Array(str.length);
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
bytes[i] = str.charCodeAt(i)
|
bytes[i] = str.charCodeAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
convertArrayBufferViewToString(buffer) {
|
convertArrayBufferViewToString(buffer) {
|
||||||
let str = ''
|
let str = '';
|
||||||
for (let i = 0; i < buffer.byteLength; i++) {
|
for (let i = 0; i < buffer.byteLength; i++) {
|
||||||
str += String.fromCharCode(buffer[i])
|
str += String.fromCharCode(buffer[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return str
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
createEncryptDecryptKeys() {
|
createEncryptDecryptKeys() {
|
||||||
@ -38,8 +38,8 @@ export default class Crypto {
|
|||||||
hash: { name: 'SHA-1' },
|
hash: { name: 'SHA-1' },
|
||||||
},
|
},
|
||||||
true, // whether the key is extractable (i.e. can be used in exportKey)
|
true, // whether the key is extractable (i.e. can be used in exportKey)
|
||||||
['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'] // must be ['encrypt', 'decrypt'] or ['wrapKey', 'unwrapKey']
|
['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], // must be ['encrypt', 'decrypt'] or ['wrapKey', 'unwrapKey']
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createSecretKey() {
|
createSecretKey() {
|
||||||
@ -49,8 +49,8 @@ export default class Crypto {
|
|||||||
length: 256, // can be 128, 192, or 256
|
length: 256, // can be 128, 192, or 256
|
||||||
},
|
},
|
||||||
true, // whether the key is extractable (i.e. can be used in exportKey)
|
true, // whether the key is extractable (i.e. can be used in exportKey)
|
||||||
['encrypt', 'decrypt'] // can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
|
['encrypt', 'decrypt'], // can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createSigningKey() {
|
createSigningKey() {
|
||||||
@ -60,8 +60,8 @@ export default class Crypto {
|
|||||||
hash: { name: 'SHA-256' },
|
hash: { name: 'SHA-256' },
|
||||||
},
|
},
|
||||||
true, // whether the key is extractable (i.e. can be used in exportKey)
|
true, // whether the key is extractable (i.e. can be used in exportKey)
|
||||||
['sign', 'verify'] // can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
|
['sign', 'verify'], // can be 'encrypt', 'decrypt', 'wrapKey', or 'unwrapKey'
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptMessage(data, secretKey, iv) {
|
encryptMessage(data, secretKey, iv) {
|
||||||
@ -73,8 +73,8 @@ export default class Crypto {
|
|||||||
iv,
|
iv,
|
||||||
},
|
},
|
||||||
secretKey, // from generateKey or importKey above
|
secretKey, // from generateKey or importKey above
|
||||||
data // ArrayBuffer of data you want to encrypt
|
data, // ArrayBuffer of data you want to encrypt
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptMessage(data, secretKey, iv) {
|
decryptMessage(data, secretKey, iv) {
|
||||||
@ -84,31 +84,31 @@ export default class Crypto {
|
|||||||
iv, // The initialization vector you used to encrypt
|
iv, // The initialization vector you used to encrypt
|
||||||
},
|
},
|
||||||
secretKey, // from generateKey or importKey above
|
secretKey, // from generateKey or importKey above
|
||||||
data // ArrayBuffer of the data
|
data, // ArrayBuffer of the data
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
importEncryptDecryptKey(jwkData, format = 'jwk', ops) {
|
importEncryptDecryptKey(jwkData, format = 'jwk', ops) {
|
||||||
const hashObj = {
|
const hashObj = {
|
||||||
name: 'RSA-OAEP',
|
name: 'RSA-OAEP',
|
||||||
hash: { name: 'SHA-1' },
|
hash: { name: 'SHA-1' },
|
||||||
}
|
};
|
||||||
|
|
||||||
return this.crypto.subtle.importKey(
|
return this.crypto.subtle.importKey(
|
||||||
format, // can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
|
format, // can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
|
||||||
jwkData,
|
jwkData,
|
||||||
hashObj,
|
hashObj,
|
||||||
true, // whether the key is extractable (i.e. can be used in exportKey)
|
true, // whether the key is extractable (i.e. can be used in exportKey)
|
||||||
ops || ['encrypt', 'wrapKey'] // 'encrypt' or 'wrapKey' for public key import or
|
ops || ['encrypt', 'wrapKey'], // 'encrypt' or 'wrapKey' for public key import or
|
||||||
// 'decrypt' or 'unwrapKey' for private key imports
|
// 'decrypt' or 'unwrapKey' for private key imports
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
exportKey(key, format) {
|
exportKey(key, format) {
|
||||||
return this.crypto.subtle.exportKey(
|
return this.crypto.subtle.exportKey(
|
||||||
format || 'jwk', // can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
|
format || 'jwk', // can be 'jwk' (public or private), 'spki' (public only), or 'pkcs8' (private only)
|
||||||
key // can be a publicKey or privateKey, as long as extractable was true
|
key, // can be a publicKey or privateKey, as long as extractable was true
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
signMessage(data, keyToSignWith) {
|
signMessage(data, keyToSignWith) {
|
||||||
@ -118,8 +118,8 @@ export default class Crypto {
|
|||||||
hash: { name: 'SHA-256' },
|
hash: { name: 'SHA-256' },
|
||||||
},
|
},
|
||||||
keyToSignWith, // from generateKey or importKey above
|
keyToSignWith, // from generateKey or importKey above
|
||||||
data // ArrayBuffer of data you want to sign
|
data, // ArrayBuffer of data you want to sign
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyPayload(signature, data, keyToVerifyWith) {
|
verifyPayload(signature, data, keyToVerifyWith) {
|
||||||
@ -131,20 +131,15 @@ export default class Crypto {
|
|||||||
},
|
},
|
||||||
keyToVerifyWith, // from generateKey or importKey above
|
keyToVerifyWith, // from generateKey or importKey above
|
||||||
signature, // ArrayBuffer of the signature
|
signature, // ArrayBuffer of the signature
|
||||||
data // ArrayBuffer of the data
|
data, // ArrayBuffer of the data
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapKey(keyToWrap, keyToWrapWith, format = 'jwk') {
|
wrapKey(keyToWrap, keyToWrapWith, format = 'jwk') {
|
||||||
return this.crypto.subtle.wrapKey(
|
return this.crypto.subtle.wrapKey(format, keyToWrap, keyToWrapWith, {
|
||||||
format,
|
name: 'RSA-OAEP',
|
||||||
keyToWrap,
|
hash: { name: 'SHA-1' },
|
||||||
keyToWrapWith,
|
});
|
||||||
{
|
|
||||||
name: 'RSA-OAEP',
|
|
||||||
hash: { name: 'SHA-1' },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapKey(
|
unwrapKey(
|
||||||
@ -154,7 +149,7 @@ export default class Crypto {
|
|||||||
unwrapAlgo,
|
unwrapAlgo,
|
||||||
unwrappedKeyAlgo, // AES-CBC for session, HMAC for signing
|
unwrappedKeyAlgo, // AES-CBC for session, HMAC for signing
|
||||||
extractable = true,
|
extractable = true,
|
||||||
keyUsages// verify for signing // decrypt for session
|
keyUsages, // verify for signing // decrypt for session
|
||||||
) {
|
) {
|
||||||
return this.crypto.subtle.unwrapKey(
|
return this.crypto.subtle.unwrapKey(
|
||||||
format,
|
format,
|
||||||
@ -163,7 +158,7 @@ export default class Crypto {
|
|||||||
unwrapAlgo,
|
unwrapAlgo,
|
||||||
unwrappedKeyAlgo,
|
unwrappedKeyAlgo,
|
||||||
extractable,
|
extractable,
|
||||||
keyUsages
|
keyUsages,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
export const getSelectedText = () => {
|
export const getSelectedText = () => {
|
||||||
let text = ''
|
let text = '';
|
||||||
if (typeof window.getSelection !== 'undefined') {
|
if (typeof window.getSelection !== 'undefined') {
|
||||||
text = window.getSelection().toString()
|
text = window.getSelection().toString();
|
||||||
} else if (typeof document.selection !== 'undefined' && document.selection.type === 'Text') {
|
} else if (typeof document.selection !== 'undefined' && document.selection.type === 'Text') {
|
||||||
text = document.selection.createRange().text
|
text = document.selection.createRange().text;
|
||||||
}
|
}
|
||||||
return text
|
return text;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const hasTouchSupport =
|
export const hasTouchSupport =
|
||||||
('ontouchstart' in window)
|
'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch);
|
||||||
|| (window.DocumentTouch && document instanceof window.DocumentTouch)
|
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
export const getObjectUrl = (encodedFile, fileType) => {
|
export const getObjectUrl = (encodedFile, fileType) => {
|
||||||
const b64 = unescape(encodedFile)
|
const b64 = unescape(encodedFile);
|
||||||
const sliceSize = 1024
|
const sliceSize = 1024;
|
||||||
const byteCharacters = window.atob(b64)
|
const byteCharacters = window.atob(b64);
|
||||||
const byteArrays = []
|
const byteArrays = [];
|
||||||
|
|
||||||
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
||||||
const slice = byteCharacters.slice(offset, offset + sliceSize)
|
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
||||||
|
|
||||||
const byteNumbers = new Array(slice.length)
|
const byteNumbers = new Array(slice.length);
|
||||||
for (let i = 0; i < slice.length; i++) {
|
for (let i = 0; i < slice.length; i++) {
|
||||||
byteNumbers[i] = slice.charCodeAt(i)
|
byteNumbers[i] = slice.charCodeAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteArray = new Uint8Array(byteNumbers)
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
|
||||||
byteArrays.push(byteArray)
|
byteArrays.push(byteArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (byteArrays.length <= 0) {
|
if (byteArrays.length <= 0) {
|
||||||
return ''
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = new window.Blob(byteArrays, { type: fileType })
|
const blob = new window.Blob(byteArrays, { type: fileType });
|
||||||
|
|
||||||
const url = window.URL.createObjectURL(blob)
|
const url = window.URL.createObjectURL(blob);
|
||||||
return url
|
return url;
|
||||||
}
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
export function sanitize(str) {
|
export function sanitize(str) {
|
||||||
return str.replace(/[^A-Za-z0-9._]/g, '-').replace(/[<>]/ig, '')
|
return str.replace(/[^A-Za-z0-9._]/g, '-').replace(/[<>]/gi, '');
|
||||||
}
|
}
|
||||||
|
@ -1,122 +1,113 @@
|
|||||||
import Crypto from './crypto'
|
import Crypto from './crypto';
|
||||||
|
|
||||||
const crypto = new Crypto()
|
const crypto = new Crypto();
|
||||||
|
|
||||||
export const process = (payload, state) => new Promise(async (resolve, reject) => {
|
export const process = (payload, state) =>
|
||||||
const privateKeyJson = state.user.privateKey
|
new Promise(async (resolve, reject) => {
|
||||||
const privateKey = await crypto.importEncryptDecryptKey(privateKeyJson, 'jwk', ['decrypt', 'unwrapKey'])
|
const privateKeyJson = state.user.privateKey;
|
||||||
|
const privateKey = await crypto.importEncryptDecryptKey(privateKeyJson, 'jwk', ['decrypt', 'unwrapKey']);
|
||||||
|
|
||||||
let sessionKey
|
let sessionKey;
|
||||||
let signingKey
|
let signingKey;
|
||||||
|
|
||||||
const iv = await crypto.convertStringToArrayBufferView(payload.iv)
|
const iv = await crypto.convertStringToArrayBufferView(payload.iv);
|
||||||
const signature = await crypto.convertStringToArrayBufferView(payload.signature)
|
const signature = await crypto.convertStringToArrayBufferView(payload.signature);
|
||||||
const payloadBuffer = await crypto.convertStringToArrayBufferView(payload.payload)
|
const payloadBuffer = await crypto.convertStringToArrayBufferView(payload.payload);
|
||||||
|
|
||||||
await new Promise((resolvePayload) => {
|
await new Promise(resolvePayload => {
|
||||||
payload.keys.forEach(async (key) => {
|
payload.keys.forEach(async key => {
|
||||||
try {
|
try {
|
||||||
sessionKey = await crypto.unwrapKey(
|
sessionKey = await crypto.unwrapKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
key.sessionKey,
|
key.sessionKey,
|
||||||
privateKey,
|
privateKey,
|
||||||
{
|
{
|
||||||
name: 'RSA-OAEP',
|
name: 'RSA-OAEP',
|
||||||
hash: { name: 'SHA-1' },
|
hash: { name: 'SHA-1' },
|
||||||
},
|
},
|
||||||
{ name: 'AES-CBC' },
|
{ name: 'AES-CBC' },
|
||||||
true,
|
true,
|
||||||
['decrypt']
|
['decrypt'],
|
||||||
)
|
);
|
||||||
|
|
||||||
signingKey = await crypto.unwrapKey(
|
signingKey = await crypto.unwrapKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
key.signingKey,
|
key.signingKey,
|
||||||
privateKey,
|
privateKey,
|
||||||
{
|
{
|
||||||
name: 'RSA-OAEP',
|
name: 'RSA-OAEP',
|
||||||
hash: { name: 'SHA-1' },
|
hash: { name: 'SHA-1' },
|
||||||
},
|
},
|
||||||
{ name: 'HMAC', hash: { name: 'SHA-256' } },
|
{ name: 'HMAC', hash: { name: 'SHA-256' } },
|
||||||
true,
|
true,
|
||||||
['verify']
|
['verify'],
|
||||||
)
|
);
|
||||||
resolvePayload()
|
resolvePayload();
|
||||||
} catch (e) { } // eslint-disable-line
|
} catch (e) {} // eslint-disable-line
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const verified = await crypto.verifyPayload(
|
const verified = await crypto.verifyPayload(signature, payloadBuffer, signingKey);
|
||||||
signature,
|
|
||||||
payloadBuffer,
|
|
||||||
signingKey
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
reject()
|
reject();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedPayload = await crypto.decryptMessage(
|
const decryptedPayload = await crypto.decryptMessage(payloadBuffer, sessionKey, iv);
|
||||||
payloadBuffer,
|
|
||||||
sessionKey,
|
|
||||||
iv
|
|
||||||
)
|
|
||||||
|
|
||||||
const payloadJson = JSON.parse(crypto.convertArrayBufferViewToString(new Uint8Array(decryptedPayload)))
|
const payloadJson = JSON.parse(crypto.convertArrayBufferViewToString(new Uint8Array(decryptedPayload)));
|
||||||
|
|
||||||
resolve(payloadJson)
|
resolve(payloadJson);
|
||||||
})
|
});
|
||||||
|
|
||||||
export const prepare = (payload, state) => new Promise(async (resolve) => {
|
export const prepare = (payload, state) =>
|
||||||
const myUsername = state.user.username
|
new Promise(async resolve => {
|
||||||
const myId = state.user.id
|
const myUsername = state.user.username;
|
||||||
|
const myId = state.user.id;
|
||||||
|
|
||||||
const sessionKey = await crypto.createSecretKey()
|
const sessionKey = await crypto.createSecretKey();
|
||||||
const signingKey = await crypto.createSigningKey()
|
const signingKey = await crypto.createSigningKey();
|
||||||
const iv = await crypto.crypto.getRandomValues(new Uint8Array(16))
|
const iv = await crypto.crypto.getRandomValues(new Uint8Array(16));
|
||||||
|
|
||||||
const jsonToSend = {
|
const jsonToSend = {
|
||||||
...payload,
|
...payload,
|
||||||
payload: {
|
payload: {
|
||||||
...payload.payload,
|
...payload.payload,
|
||||||
sender: myId,
|
sender: myId,
|
||||||
username: myUsername,
|
username: myUsername,
|
||||||
text: encodeURI(payload.payload.text),
|
text: encodeURI(payload.payload.text),
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const payloadBuffer = crypto.convertStringToArrayBufferView(JSON.stringify(jsonToSend))
|
const payloadBuffer = crypto.convertStringToArrayBufferView(JSON.stringify(jsonToSend));
|
||||||
|
|
||||||
const encryptedPayload = await crypto.encryptMessage(payloadBuffer, sessionKey, iv)
|
const encryptedPayload = await crypto.encryptMessage(payloadBuffer, sessionKey, iv);
|
||||||
const payloadString = await crypto.convertArrayBufferViewToString(new Uint8Array(encryptedPayload))
|
const payloadString = await crypto.convertArrayBufferViewToString(new Uint8Array(encryptedPayload));
|
||||||
|
|
||||||
const signature = await crypto.signMessage(encryptedPayload, signingKey)
|
const signature = await crypto.signMessage(encryptedPayload, signingKey);
|
||||||
|
|
||||||
const encryptedKeys = await Promise.all(state.room.members
|
const encryptedKeys = await Promise.all(
|
||||||
.map(async (member) => {
|
state.room.members.map(async member => {
|
||||||
const key = await crypto.importEncryptDecryptKey(member.publicKey)
|
const key = await crypto.importEncryptDecryptKey(member.publicKey);
|
||||||
const enc = await Promise.all([
|
const enc = await Promise.all([crypto.wrapKey(sessionKey, key), crypto.wrapKey(signingKey, key)]);
|
||||||
crypto.wrapKey(sessionKey, key),
|
return {
|
||||||
crypto.wrapKey(signingKey, key),
|
sessionKey: enc[0],
|
||||||
])
|
signingKey: enc[1],
|
||||||
return {
|
};
|
||||||
sessionKey: enc[0],
|
}),
|
||||||
signingKey: enc[1],
|
);
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const ivString = await crypto.convertArrayBufferViewToString(new Uint8Array(iv))
|
const ivString = await crypto.convertArrayBufferViewToString(new Uint8Array(iv));
|
||||||
const signatureString = await crypto.convertArrayBufferViewToString(new Uint8Array(signature))
|
const signatureString = await crypto.convertArrayBufferViewToString(new Uint8Array(signature));
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
toSend: {
|
toSend: {
|
||||||
payload: payloadString,
|
payload: payloadString,
|
||||||
signature: signatureString,
|
signature: signatureString,
|
||||||
iv: ivString,
|
iv: ivString,
|
||||||
keys: encryptedKeys,
|
keys: encryptedKeys,
|
||||||
},
|
},
|
||||||
original: jsonToSend,
|
original: jsonToSend,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import socketIO from 'socket.io-client'
|
import socketIO from 'socket.io-client';
|
||||||
import generateUrl from '../api/generator';
|
import generateUrl from '../api/generator';
|
||||||
|
|
||||||
let socket
|
let socket;
|
||||||
|
|
||||||
export const connect = (roomId) => {
|
export const connect = roomId => {
|
||||||
socket = socketIO(generateUrl(), {
|
socket = socketIO(generateUrl(), {
|
||||||
query: {
|
query: {
|
||||||
roomId,
|
roomId,
|
||||||
},
|
},
|
||||||
forceNew: true,
|
forceNew: true,
|
||||||
})
|
});
|
||||||
return socket
|
return socket;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getSocket = () => socket
|
export const getSocket = () => socket;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user