",
 7 |   "license": "GPLv3",
 8 |   "private": true,
 9 |   "dependencies": {
10 |     "about-window": "^1.15.2",
11 |     "electron-json-storage": "^4.5.0",
12 |     "electron-log": "^4.4.6",
13 |     "electron-progressbar": "^2.0.1",
14 |     "electron-updater": "5.0.6",
15 |     "electron-windows-badge": "^1.1.0",
16 |     "titlebar": "saghul/titlebar.git"
17 |   }
18 | }
19 | 
--------------------------------------------------------------------------------
/build/afterSignHook.js:
--------------------------------------------------------------------------------
 1 | require('dotenv').config()
 2 | const fs = require('fs');
 3 | const path = require('path');
 4 | const { notarize } = require('@electron/notarize');
 5 | 
 6 | module.exports = async function(params) {
 7 |     // Only notarize the app on Mac OS only.
 8 |     if (params.electronPlatformName !== 'darwin') {
 9 |         return;
10 |     }
11 | 
12 |     if (!process.env.APPLE_ID) {
13 |         console.warn('Cannot find appleId in env to notarize application, skipping notarization');
14 |         return;
15 |     }
16 |     console.log('afterSign hook triggered', params);
17 | 
18 |     // Same appId in electron-builder.
19 |     let appId = 'com.agprojects.Sylk'
20 | 
21 |     let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`);
22 |     if (!fs.existsSync(appPath)) {
23 |         throw new Error(`Cannot find application at: ${appPath}`);
24 |     }
25 | 
26 |     if (!process.env.APPLE_ID_PASSWORD) {
27 |         throw new Error('Cannot find appleIdPassword in env to notarize application');
28 |     }
29 | 
30 |     console.log(`Notarizing ${appId} found at ${appPath}`);
31 | 
32 |     try {
33 |         await notarize({
34 |             appBundleId: appId,
35 |             appPath: appPath,
36 |             appleId: process.env.APPLE_ID,
37 |             appleIdPassword: process.env.APPLE_ID_PASSWORD,
38 |             teamId: process.env.TEAM_ID
39 |         });
40 |     } catch (error) {
41 |         console.error(error);
42 |     }
43 | 
44 |     console.log(`Done notarizing ${appId}`);
45 | };
46 | 
--------------------------------------------------------------------------------
/build/entitlements.mac.inherit.plist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 |     com.apple.security.inherit 
 6 |     com.apple.security.cs.allow-jit 
 8 |     com.apple.security.cs.allow-dyld-environment-variables 
10 |      
12 |  
13 | 
--------------------------------------------------------------------------------
/build/entitlements.mac.plist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 |     com.apple.security.device.bluetooth 
 6 |     com.apple.security.device.camera 
 8 |     com.apple.security.device.microphone 
10 |     com.apple.security.device.audio-input 
12 |     com.apple.security.device.usb 
14 |     com.apple.security.network.client 
16 |     com.apple.security.network.server 
18 |     com.apple.security.cs.allow-jit 
20 |     com.apple.security.cs.allow-unsigned-executable-memory 
22 |     com.apple.security.cs.allow-dyld-environment-variables 
24 |     com.apple.security.application-groups 
26 |     
27 |         com.agprojects.Sylk 
28 |      
29 |  
30 |  
31 | 
--------------------------------------------------------------------------------
/build/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AGProjects/sylk-webrtc/57ba33d212b657fafa5ecb8fa13abdd83774e094/build/icon.icns
--------------------------------------------------------------------------------
/build/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AGProjects/sylk-webrtc/57ba33d212b657fafa5ecb8fa13abdd83774e094/build/icon.ico
--------------------------------------------------------------------------------
/build/icons/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AGProjects/sylk-webrtc/57ba33d212b657fafa5ecb8fa13abdd83774e094/build/icons/512.png
--------------------------------------------------------------------------------
/configure:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | yarn install
3 | 
--------------------------------------------------------------------------------
/examples/apache/.htaccess:
--------------------------------------------------------------------------------
1 | 
2 |     RewriteEngine On
3 |     RewriteBase /
4 |     RewriteRule ^index\.html$ - [L]
5 |     RewriteCond %{REQUEST_FILENAME} !-f
6 |     RewriteCond %{REQUEST_FILENAME} !-d
7 |     RewriteRule . /index.html [L]
8 |  
9 | 
--------------------------------------------------------------------------------
/src/app/MaterialColors.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const { colors } = require('@material-ui/core');
 4 | const murmur = require('murmurhash-js');
 5 | 
 6 | // Available material design colors
 7 | const availableColors = [
 8 |     colors.red,         colors.pink,    colors.purple,      colors.deepPurple,
 9 |     colors.indigo,      colors.blue,    colors.lightBlue,   colors.cyan,
10 |     colors.teal,        colors.green,   colors.lightGreen,  colors.lime,
11 |     colors.yellow,      colors.amber,   colors.orange,      colors.deepOrange,
12 |     colors.brown,       colors.grey,    colors.blueGrey
13 | ];
14 | 
15 | function generateColor(text) {
16 |     return availableColors[(murmur.murmur3(text) % availableColors.length)];
17 | }
18 | 
19 | exports.generateColor = generateColor;
20 | 
--------------------------------------------------------------------------------
/src/app/MaterialUIAsBootstrap.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React = require('react');
  4 | const { withStyles, makeStyles } = require('@material-ui/core/styles');
  5 | const { Button, Tab, Tabs, Tooltip } = require('@material-ui/core');
  6 | const { InputBase } = require('@material-ui/core');
  7 | 
  8 | const BootstrapButton = withStyles({
  9 |     root: {
 10 |         boxShadow: 'none',
 11 |         textTransform: 'none',
 12 |         fontSize: 14,
 13 |         fontFamily: 'inherit',
 14 |         fontWeight: 'normal',
 15 |         padding: '6px 12px',
 16 |         border: '1px solid transparent',
 17 |         lineHeight: 1.42857143,
 18 |         borderRadius: '4px'
 19 |     },
 20 |     contained: {
 21 |         backgroundColor: '#337ab7',
 22 |         borderColor: '#2e6da4',
 23 |         color: '#fff',
 24 |         '&:hover': {
 25 |             backgroundColor: '#286090',
 26 |             borderColor: '#204d74',
 27 |             boxShadow: 'none'
 28 |         },
 29 |         '&:active': {
 30 |             boxShadow: 'inset 0 3px 5px rgba(0, 0, 0, .125)',
 31 |             backgroundColor: '#286090',
 32 |             borderColor: '#204d74'
 33 |         },
 34 |         '&:focus': {
 35 |             borderColor: '#122b40',
 36 |             backgroundColor: '#204d74',
 37 |             outlineOffset: '-2px',
 38 |             boxShadow: 'inset 0px 3px 5px 0px rgba(0,0,0,.125)'
 39 |         }
 40 |     },
 41 |     disabled: {
 42 |         border: '1px solid #fff',
 43 |         cursor: 'not-allowed'
 44 |     },
 45 |     sizeLarge: {
 46 |         padding: '10px 20px',
 47 |         fontSize: 18,
 48 |         fontWeight: 'bold',
 49 |         borderRadius: '6px',
 50 |         lineHeight: 1.33333
 51 |     },
 52 |     label: {
 53 |         display: 'block'
 54 |     },
 55 |     text: {
 56 |         borderColor: 'transparent',
 57 |         backgroundColor: 'transparent',
 58 |         color: '#337ab7',
 59 |         boxShadow: 'none'
 60 |     }
 61 | })(Button);
 62 | 
 63 | 
 64 | const BootstrapInputBase = withStyles((theme) => ({
 65 |     root: {
 66 |         'label + &': {
 67 |             marginTop: theme.spacing(3)
 68 |         },
 69 |         fontFamily: 'inherit'
 70 |     },
 71 |     input: {
 72 |         borderRadius: 4,
 73 |         position: 'relative',
 74 |         backgroundColor: theme.palette.background.paper,
 75 |         border: '1px solid #ced4da',
 76 |         fontSize: 14,
 77 |         padding: '10px 26px 10px 12px',
 78 |         transition: theme.transitions.create(['border-color', 'box-shadow']),
 79 |         boxShadow: 'inset 0 1px 1px rgba(0, 0, 0, .075)',
 80 |         '&:focus': {
 81 |             borderRadius: 4,
 82 |             borderColor: '#66afe9',
 83 |             boxShadow: 'inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6)'
 84 |         }
 85 |     }
 86 | }))(InputBase);
 87 | 
 88 | const BootstrapTabs = withStyles({
 89 |     root: {
 90 |         borderBottom: '1px solid rgba(0, 0, 0, .12)'
 91 |     },
 92 |     indicator: {
 93 |         backgroundColor: '#337ab7'
 94 |     }
 95 | })(Tabs);
 96 | 
 97 | const BootstrapTab = withStyles((theme) => ({
 98 |     root: {
 99 |         textTransform: 'none',
100 |         fontWeight: theme.typography.fontWeightRegular,
101 |         fontFamily: 'inherit',
102 |         fontSize: '14px',
103 |         '&:hover': {
104 |             color: '#23527c',
105 |             opacity: 1
106 |         },
107 |         '&$selected': {
108 |             color: '#337ab7',
109 |             fontWeight: theme.typography.fontWeightMedium
110 |         },
111 |         '&:focus': {
112 |             color: '#337ab7'
113 |         }
114 |     },
115 |     selected: {}
116 | }))(Tab);
117 | 
118 | 
119 | const useStylesBootstrap = makeStyles((theme) => ({
120 |     arrow: {
121 |         color: theme.palette.common.black
122 |     },
123 |     tooltip: {
124 |         backgroundColor: theme.palette.common.black,
125 |         fontSize: '12px',
126 |         fontFamily: 'inherit'
127 |     }
128 | }));
129 | 
130 | function BootstrapTooltip(props) {
131 |     const classes = useStylesBootstrap();
132 | 
133 |     return 
12 |             
13 |                 About Sylk 
14 |              
15 |             
16 |                 
17 |                 Sylk client is part of Sylk Suite , a set of
18 |                 applications for real-time communications using SIP and WebRTC specifications
19 |                 
20 |                 Copyright © AG Projects 
22 |              
23 |          
24 |     );
25 | }
26 | 
27 | AboutModal.propTypes = {
28 |     show: PropTypes.bool.isRequired,
29 |     close: PropTypes.func.isRequired
30 | };
31 | 
32 | 
33 | module.exports = AboutModal;
34 | 
--------------------------------------------------------------------------------
/src/app/components/AudioPlayer.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React     = require('react');
 4 | const PropTypes = require('prop-types');
 5 | // const play = require('audio-play');
 6 | const ac = require('audio-context')();
 7 | 
 8 | const utils = require('../utils');
 9 | 
10 | 
11 | class AudioPlayer extends React.Component {
12 |     constructor(props) {
13 |         super(props);
14 |         this.timeout = null;
15 | 
16 |         this.buffer = null;
17 |         this.time = null;
18 |         this.src = null
19 | 
20 |         // ES6 classes no longer autobind
21 |         this.audioEnded = this.audioEnded.bind(this);
22 |         this.stop = this.stop.bind(this);
23 |     }
24 | 
25 |     componentDidMount() {
26 |         utils.loadAudio(this.props.sourceFile, ac).then(
27 |             (buffer) => {
28 |                 this.buffer = buffer
29 |                 this.time = ac.currentTime;
30 |             }
31 |         );
32 |     }
33 | 
34 |     audioEnded() {
35 |         this.timeout = setTimeout(() => {
36 |             let source = ac.createBufferSource();
37 |             source.buffer = this.buffer;
38 |             source.addEventListener('ended', this.audioEnded);
39 |             source.connect(ac.destination);
40 |             source.start(this.time || ac.currentTime);
41 |             this.src = source;
42 |         }, 3000);
43 |     }
44 | 
45 |     componentWillUnmount() {
46 |         clearTimeout(this.timeout);
47 |         this.timeout = null;
48 |         if (this.src !== null) {
49 |             this.src.removeEventListener('ended', this.audioEnded);
50 |             this.src = null;
51 |         }
52 |     }
53 | 
54 |     play(repeat) {
55 |         let source = ac.createBufferSource();
56 |         source.buffer = this.buffer;
57 | 
58 |         if (repeat) {
59 |             this.timeout = null;
60 |             source.addEventListener('ended', this.audioEnded);
61 |         } else {
62 |             source.addEventListener('ended', this.stop);
63 |         }
64 |         source.connect(ac.destination);
65 |         source.start(this.time || ac.currentTime);
66 |         this.src = source;
67 |     }
68 | 
69 |     stop() {
70 |         if (this.src !== null) {
71 |             // this.src.stop();
72 |             this.src.removeEventListener('ended', this.audioEnded);
73 |             this.src = null;
74 |         }
75 |         clearTimeout(this.timeout);
76 |         this.timeout = null;
77 |     }
78 | 
79 |     render() {
80 |         return (
);
81 |     }
82 | }
83 | 
84 | AudioPlayer.propTypes = {
85 |     sourceFile: PropTypes.string.isRequired
86 | };
87 | 
88 | 
89 | module.exports = AudioPlayer;
90 | 
--------------------------------------------------------------------------------
/src/app/components/CallByUriBox.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React      = require('react');
  4 | const PropTypes  = require('prop-types');
  5 | const { default: clsx } = require('clsx');
  6 | 
  7 | const Call       = require('./Call');
  8 | const FooterBox  = require('./FooterBox');
  9 | const PreMedia   = require('./PreMedia');
 10 | 
 11 | class CallByUriBox extends React.Component {
 12 |     constructor(props) {
 13 |         super(props);
 14 |         this.state = {
 15 |             displayName: ''
 16 |         };
 17 | 
 18 |         this._notificationCenter = null;
 19 | 
 20 |         // ES6 classes no longer autobind
 21 |         this.handleDisplayNameChange = this.handleDisplayNameChange.bind(this);
 22 |         this.handleSubmit = this.handleSubmit.bind(this);
 23 |         this.callStateChanged = this.callStateChanged.bind(this);
 24 |     }
 25 | 
 26 |     componentDidMount() {
 27 |         if (!this.props.localMedia) {
 28 |             this.props.getLocalMedia();
 29 |         }
 30 |         this._notificationCenter = this.props.notificationCenter();
 31 |     }
 32 | 
 33 |     componentDidUpdate(prevProps, prevState) {
 34 |         if (!prevProps.currentCall && this.props.currentCall) {
 35 |             this.props.currentCall.on('stateChanged', this.callStateChanged);
 36 |         }
 37 |     }
 38 | 
 39 |     callStateChanged(oldState, newState, data) {
 40 |         if (newState === 'terminated') {
 41 |             this._notificationCenter.postSystemNotification('Thanks for calling with Sylk!', {timeout: 10});
 42 |         }
 43 |     }
 44 | 
 45 |     handleDisplayNameChange(event) {
 46 |         this.setState({displayName: event.target.value});
 47 |     }
 48 | 
 49 |     handleSubmit(event) {
 50 |         event.preventDefault();
 51 |         this.props.handleCallByUri(this.state.displayName, this.props.targetUri);
 52 |     }
 53 | 
 54 |     render() {
 55 |         const validInput = this.state.displayName !== '';
 56 |         let content;
 57 | 
 58 |         if (this.props.account !== null && this.props.localMedia) {
 59 |             content = (
 60 |                 
 83 |                     
 86 |                     
You've been invited to call{this.props.targetUri}  
 87 |                     
103 |                 
109 |                 {!this.props.account && this.props.localMedia && 
}
110 |                 
111 |                     {content}
112 |                 
113 |             
13 |             
14 |                 
15 |                 { props.targetUri === ''
16 |                     ? 
17 |                         
We hope you enjoyed this {props.wasCall === true ? 'call' : 'conference'}.
18 |                         
Download 
19 |                         
20 |                         
Or you can {props.wasCall === true ? 'call' : 'join'} again:
21 |                         
22 |                              
24 |                     
25 |                     : 
26 |                         
The {props.wasCall === true ? 'call' : 'conference'} cannot be completed at this moment.{props.failureReason} 
27 |                         
Try again 
28 |                     
29 |                 }
30 |             
31 |         
49 |                 
50 |                     Call me, maybe? 
51 |                  
52 |                 
53 |                     
54 |                         Share this link 
58 |                     
59 |                         
60 |                             
61 |                                  
63 |                             
64 |                                  
66 |                         
67 |                     
 
69 |              
70 |         );
71 |     }
72 | }
73 | 
74 | CallMeMaybeModal.propTypes = {
75 |     show               : PropTypes.bool.isRequired,
76 |     close              : PropTypes.func.isRequired,
77 |     callUrl            : PropTypes.string.isRequired,
78 |     notificationCenter : PropTypes.func.isRequired
79 | };
80 | 
81 | 
82 | module.exports = CallMeMaybeModal;
83 | 
--------------------------------------------------------------------------------
/src/app/components/Chat/OldMessage.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React = require('react');
  4 | const useState = React.useState;
  5 | const useEffect = React.useEffect;
  6 | const PropTypes = require('prop-types');
  7 | const ReactBootstrap = require('react-bootstrap');
  8 | const Media = ReactBootstrap.Media;
  9 | const { default: parse } = require('html-react-parser');
 10 | const linkifyUrls = require('linkify-urls');
 11 | const { Chip } = require('@material-ui/core');
 12 | const { makeStyles } = require('@material-ui/core/styles');
 13 | const {
 14 |     Lock: LockIcon
 15 | } = require('@material-ui/icons');
 16 | 
 17 | 
 18 | const styleSheet = makeStyles((theme) => ({
 19 |     chipSmall: {
 20 |         height: 18,
 21 |         fontSize: 11
 22 |     },
 23 |     iconSmall: {
 24 |         width: 12,
 25 |         height: 12
 26 |     },
 27 |     lockIcon: {
 28 |         fontSize: 15,
 29 |         verticalAlign: 'middle',
 30 |         color: '#ccc'
 31 |     }
 32 | }));
 33 | 
 34 | const Message = ({
 35 |     message
 36 | }) => {
 37 |     const classes = styleSheet();
 38 |     const [parsedContent, setParsedContent] = useState();
 39 | 
 40 |     const preHtmlEntities = (str) => {
 41 |         return String(str).replace(//g, '>').replace(/"/g, '"');
 42 |     };
 43 | 
 44 |     const postHtmlEntities = (str) => {
 45 |         return String(str).replace(/(?!&|<|>|")&/g, '&');
 46 |     };
 47 | 
 48 |     const customUrlRegexp = () => (/((?:https?(?::\/\/))(?:www\.)?(?:[a-zA-Z\d-_.]+(?:(?:\.|@)[a-zA-Z\d]{2,})|localhost)(?:(?:[-a-zA-Z\d:%_+.~#!?&//=@();]*)(?:[,](?![\s]))*)*)/g);
 49 | 
 50 |     useEffect(() => {
 51 |         if (message.contentType === 'text/html') {
 52 |             setParsedContent(parse(message.content.trim(), {
 53 |                 replace: (domNode) => {
 54 |                     if (domNode.attribs && domNode.attribs.href) {
 55 |                         domNode.attribs.target = '_blank';
 56 |                         return;
 57 |                     }
 58 |                     if (domNode.type === 'text') {
 59 |                         if (!domNode.parent || (domNode.parent.type === 'tag' && domNode.parent.name !== 'a')) {
 60 |                             let url = linkifyUrls(preHtmlEntities(domNode.data), {
 61 |                                 customUrlRegexp,
 62 |                                 attributes: {
 63 |                                     target: '_blank',
 64 |                                     rel: 'noopener noreferrer'
 65 |                                 }
 66 |                             });
 67 |                             return ({parse(postHtmlEntities(url))} );
 68 |                         }
 69 |                     }
 70 |                 }
 71 |             }));
 72 |         } else if (message.contentType.startsWith('image/')) {
 73 |             const image = `data:${message.contentType};base64,${message.content}`
 74 |             setParsedContent({parse(postHtmlEntities(linkfiedContent))} 
 86 |             );
 87 |         } else if (message.contentType === 'text/pgp-public-key') {
 88 |             setParsedContent(
 89 |                 
103 |             
104 |                 Edit message
105 |              
106 |             {parsedContent}
107 |         
108 |     );
109 | };
110 | 
111 | Message.propTypes = {
112 |     message: PropTypes.object.isRequired
113 | };
114 | 
115 | 
116 | module.exports = Message;
117 | 
--------------------------------------------------------------------------------
/src/app/components/Chat/VoiceMessageRecorderModal.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React = require('react');
  4 | const { useEffect, useRef, useState } = React;
  5 | const PropTypes = require('prop-types');
  6 | 
  7 | const {
  8 |     IconButton,
  9 |     Popover
 10 | } = require('@material-ui/core');
 11 | const {
 12 |     CancelRounded: CancelIcon
 13 | } = require('@material-ui/icons');
 14 | const { makeStyles } = require('@material-ui/core/styles');
 15 | 
 16 | const { useReactMediaRecorder } = require('react-media-recorder');
 17 | 
 18 | const VoiceMessageRecorderRenderer = require('./VoiceMessageRecorderRenderer');
 19 | 
 20 | 
 21 | const styleSheet = makeStyles({
 22 |     top: {
 23 |         position: 'absolute',
 24 |         top: -20,
 25 |         right: -20
 26 |     },
 27 |     iconSize: {
 28 |         fontSize: '2rem'
 29 |     },
 30 |     popoverRoot: {
 31 |         overflow: 'visible'
 32 |     }
 33 | });
 34 | 
 35 | const VoiceMessageRecorderModal = (props) => {
 36 |     const classes = styleSheet();
 37 | 
 38 |     const { status, startRecording, stopRecording, previewAudioStream } =
 39 |         useReactMediaRecorder({
 40 |             video: false,
 41 |             mediaRecorderOptions: { mimeType: 'audio/wav' },
 42 |             onStop: (t, u) => {
 43 |                 recordingStopped(t, u)
 44 |             }
 45 |         });
 46 | 
 47 |     const [voiceMessage, setVoiceMessage] = useState(null);
 48 |     const [isPreviewStarted, setIsPreviewStarted] = useState(false);
 49 | 
 50 |     const shouldUnmount = useRef(true);
 51 | 
 52 |     useEffect(() => {
 53 |         let ignore = false;
 54 |         if (props.show && startRecording && !isPreviewStarted) {
 55 |             if (!ignore) {
 56 |                 setIsPreviewStarted(true);
 57 |                 startRecording();
 58 |             }
 59 |         }
 60 |         return () => {
 61 |             ignore = true;
 62 |         }
 63 |     }, [startRecording, isPreviewStarted, props.show]);
 64 | 
 65 |     const recordingStopped = (blobUrl, blob) => {
 66 |         if (shouldUnmount.current !== false) {
 67 |             setVoiceMessage(blob);
 68 |         } else {
 69 |             props.close();
 70 |         }
 71 |     }
 72 | 
 73 |     const handleClose = () => {
 74 |         shouldUnmount.current = false;
 75 | 
 76 |         if (status !== 'stopped') {
 77 |             stopRecording();
 78 |         } else {
 79 |             props.close();
 80 |         }
 81 |     }
 82 | 
 83 |     return (
 84 |         
101 |             
102 |                  {
113 |                         props.sendAudioMessage([
114 |                             new File(
115 |                                 [voiceMessage],
116 |                                 'sylk-audio-recording.' + voiceMessage.type.split(';')[0].split('/')[1] || 'webm',
117 |                                 { type: voiceMessage.type }
118 |                             )
119 |                         ]);
120 |                         handleClose();
121 |                     }}
122 |                 />
123 |                 
124 |                      
126 |              
127 |          
128 | 
129 |     );
130 | }
131 | 
132 | VoiceMessageRecorderModal.propTypes = {
133 |     show: PropTypes.bool.isRequired,
134 |     close: PropTypes.func.isRequired,
135 |     sendAudioMessage: PropTypes.func.isRequired,
136 |     anchorElement: PropTypes.object.isRequired
137 | };
138 | 
139 | 
140 | module.exports = VoiceMessageRecorderModal;
141 | 
--------------------------------------------------------------------------------
/src/app/components/Conference.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React      = require('react');
  4 | const PropTypes  = require('prop-types');
  5 | const assert     = require('assert');
  6 | const debug      = require('debug');
  7 | 
  8 | const ConferenceBox = require('./ConferenceBox');
  9 | const LocalMedia    = require('./LocalMedia');
 10 | const config        = require('../config');
 11 | 
 12 | const DEBUG = debug('blinkrtc:Conference');
 13 | 
 14 | 
 15 | class Conference extends React.Component {
 16 |     constructor(props) {
 17 |         super(props);
 18 | 
 19 |         // ES6 classes no longer autobind
 20 |         this.mediaPlaying = this.mediaPlaying.bind(this);
 21 |         this.confStateChanged = this.confStateChanged.bind(this);
 22 |         this.hangup = this.hangup.bind(this);
 23 |     }
 24 | 
 25 |     confStateChanged(oldState, newState, data) {
 26 |         DEBUG(`Conference state changed ${oldState} -> ${newState}`);
 27 |         if (newState === 'established') {
 28 |             this.forceUpdate();
 29 |         }
 30 |     }
 31 | 
 32 |     start() {
 33 |         assert(this.props.currentCall == null, 'currentCall is not null');
 34 |         const options = {
 35 |             pcConfig: {iceServers: config.iceServers},
 36 |             localStream: this.props.localMedia,
 37 |             offerOptions: {
 38 |                 offerToReceiveAudio: false,
 39 |                 offerToReceiveVideo: false
 40 |             },
 41 |             initialParticipants: this.props.participantsToInvite
 42 |         };
 43 |         Object.assign(options, this.props.roomMedia);
 44 |         const confCall = this.props.account.joinConference(this.props.targetUri.toLowerCase(), options);
 45 |         confCall.on('stateChanged', this.confStateChanged);
 46 |     }
 47 | 
 48 |     hangup() {
 49 |         this.props.hangupCall();
 50 |     }
 51 | 
 52 |     mediaPlaying() {
 53 |         assert(this.props.currentCall == null, 'currentCall is not null');
 54 |         this.start();
 55 |     }
 56 | 
 57 | 
 58 |     render() {
 59 |         let box;
 60 | 
 61 |         if (this.props.localMedia !== null) {
 62 |             if (this.props.currentCall != null && this.props.currentCall.state === 'established') {
 63 |                 box = (
 64 |                     
 95 |                 {box}
 96 |             
 97 |         );
 98 |     }
 99 | }
100 | 
101 | Conference.propTypes = {
102 |     notificationCenter      : PropTypes.func.isRequired,
103 |     account                 : PropTypes.object.isRequired,
104 |     hangupCall              : PropTypes.func.isRequired,
105 |     setDevice               : PropTypes.func.isRequired,
106 |     shareScreen             : PropTypes.func.isRequired,
107 |     propagateKeyPress       : PropTypes.func.isRequired,
108 |     toggleShortcuts         : PropTypes.func.isRequired,
109 |     currentCall             : PropTypes.object,
110 |     localMedia              : PropTypes.object,
111 |     targetUri               : PropTypes.string,
112 |     participantsToInvite    : PropTypes.array,
113 |     generatedVideoTrack     : PropTypes.bool,
114 |     muteAudioFromStart      : PropTypes.bool,
115 |     participantIsGuest      : PropTypes.bool,
116 |     roomMedia               : PropTypes.object,
117 |     lowBandwidth            : PropTypes.bool,
118 |     toggleChatInCall        : PropTypes.func,
119 |     unreadMessages          : PropTypes.object
120 | 
121 | };
122 | 
123 | 
124 | module.exports = Conference;
125 | 
--------------------------------------------------------------------------------
/src/app/components/ConferenceCarousel.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React              = require('react');
  4 | const PropTypes          = require('prop-types');
  5 | const { default: TransitionGroup } = require('react-transition-group/TransitionGroup');
  6 | const { default: CSSTransition } = require('react-transition-group/CSSTransition');
  7 | const { default: clsx }  = require('clsx');
  8 | 
  9 | class ConferenceCarousel extends React.Component {
 10 |     constructor(props) {
 11 |         super(props);
 12 |         this.state = {
 13 |             displayLeftArrow: false,
 14 |             displayRightArrow: false
 15 |         };
 16 | 
 17 |         this.carouselList = null;
 18 | 
 19 |         // ES6 classes no longer autobind
 20 |         this.handleScroll = this.handleScroll.bind(this);
 21 |         this.handleResize = this.handleResize.bind(this);
 22 |         this.scrollToRight = this.scrollToRight.bind(this);
 23 |         this.scrollToLeft = this.scrollToLeft.bind(this);
 24 |     }
 25 | 
 26 |     componentDidMount() {
 27 |         // Get UL from children of the carousel
 28 |         const children = this.refs.carousel.children;
 29 |         for (let child of children) {
 30 |             if (child.tagName == 'UL') {
 31 |                 this.carouselList = child;
 32 |             }
 33 |         };
 34 | 
 35 |         if (this.canScroll()) {
 36 |             this.setState({displayRightArrow: true});   // eslint-disable-line react/no-did-mount-set-state
 37 |         }
 38 | 
 39 |         window.addEventListener('resize', this.handleResize);
 40 |     }
 41 | 
 42 |     componentWillUnmount() {
 43 |         window.removeEventListener('resize', this.handleResize);
 44 |     }
 45 | 
 46 |     componentDidUpdate(prevProps) {
 47 |         if (prevProps.children.length != this.props.children.length) {
 48 |             // We need to wait for the animation to end before calculating
 49 |             setTimeout(() => {
 50 |                 this.handleScroll();
 51 |             }, 310);
 52 |         }
 53 |     }
 54 | 
 55 |     canScroll() {
 56 |         return (this.carouselList.scrollWidth > this.carouselList.clientWidth);
 57 |     }
 58 | 
 59 |     handleScroll(event) {
 60 |         const newState = {
 61 |             displayRightArrow : false,
 62 |             displayLeftArrow  : false
 63 |         };
 64 | 
 65 |         if (this.canScroll()) {
 66 |             const scrollWidth = this.carouselList.scrollWidth;
 67 |             const scrollLeft = this.carouselList.scrollLeft;
 68 |             const clientWidth = this.carouselList.clientWidth;
 69 |             newState.displayRightArrow = true;
 70 |             if (scrollLeft > 0) {
 71 |                 newState.displayLeftArrow = true;
 72 |                 if (scrollLeft === (scrollWidth - clientWidth)) {
 73 |                     newState.displayRightArrow = false;
 74 |                 }
 75 |             } else {
 76 |                 newState.displayLeftArrow = false;
 77 |             }
 78 |         }
 79 | 
 80 |         this.setState(newState);
 81 |     }
 82 | 
 83 |     scrollToRight(event) {
 84 |         this.carouselList.scrollLeft += 100;
 85 |     }
 86 | 
 87 |     scrollToLeft(event) {
 88 |         this.carouselList.scrollLeft -= 100;
 89 |     }
 90 | 
 91 |     handleResize(event) {
 92 |         if (this.canScroll()) {
 93 |             this.setState({displayRightArrow: true})
 94 |         } else {
 95 |             if (this.state.displayRightArrow) {
 96 |                 this.setState({displayRightArrow: false});
 97 |             }
 98 |         }
 99 |     }
100 | 
101 |     render() {
102 |         const items = [];
103 |         let idx = 0;
104 |         React.Children.forEach(this.props.children, (child) => {
105 |             items.push({child} 
);
112 |         }
113 |         if (this.state.displayRightArrow) {
114 |             arrows.push(
);
115 |         }
116 |         const classes = clsx({
117 |             'carousel-list' : true,
118 |             'list-inline'   : true,
119 |             'text-right'    : this.props.align === 'right'
120 |         });
121 |         return (
122 |             
123 |                 {arrows}
124 |                 
125 |                     
126 |                         {items}
127 |                      
128 |                 
129 |             
18 |                 Shared by {uploader} 
19 |                 
25 |              
26 |         );
27 |     });
28 | 
29 |     return (
30 |         
31 |             
Shared Files 
32 |                 
33 |                     {entries}
34 |                  
35 |         
36 |     );
37 | };
38 | 
39 | ConferenceDrawerFiles.propTypes = {
40 |     sharedFiles: PropTypes.array.isRequired,
41 |     downloadFile: PropTypes.func.isRequired,
42 |     embed: PropTypes.bool,
43 |     wide: PropTypes.bool
44 | };
45 | 
46 | 
47 | module.exports = ConferenceDrawerFiles;
48 | 
--------------------------------------------------------------------------------
/src/app/components/ConferenceDrawerLog.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React         = require('react');
 4 | const PropTypes     = require('prop-types');
 5 | const { default: clsx } = require('clsx');
 6 | const utils         = require('../utils');
 7 | 
 8 | 
 9 | const ConferenceDrawerLog = (props) => {
10 |     const entries = props.log.map((elem, idx) => {
11 |         const classes = clsx({
12 |             'text-danger'   : elem.level === 'error',
13 |             'text-warning'  : elem.level === 'warning',
14 |             'log-entry'     : true
15 |         });
16 | 
17 |         const originator = elem.originator.displayName || elem.originator.uri || elem.originator;
18 | 
19 |         const messages = elem.messages.map((message, index) => {
20 |             return {message} ;
21 |         });
22 | 
23 |         const color = utils.generateMaterialColor(elem.originator.uri || elem.originator)['300'];
24 |         return (
25 |             
26 |                 
{props.log.length - idx}
27 |                 
28 |                     {originator}  {elem.action} 
30 |             
36 |             
Configuration Events 
37 |             
38 |                 {entries}
39 |              
40 |         
10 |             
11 |                  
14 |         
15 |     );
16 | };
17 | 
18 | ConferenceDrawerMute.propTypes = {
19 |     muteEverybody: PropTypes.func.isRequired
20 | };
21 | 
22 | 
23 | module.exports = ConferenceDrawerMute;
24 | 
--------------------------------------------------------------------------------
/src/app/components/ConferenceDrawerParticipant.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React             = require('react');
 4 | const useState          = React.useState;
 5 | const useEffect         = React.useEffect;
 6 | const PropTypes         = require('prop-types');
 7 | const ReactBootstrap    = require('react-bootstrap');
 8 | const Label             = ReactBootstrap.Label;
 9 | const Media             = ReactBootstrap.Media;
10 | const ButtonGroup       = ReactBootstrap.ButtonGroup;
11 | const hark              = require('hark');
12 | 
13 | const UserIcon    = require('./UserIcon');
14 | const HandIcon    = require('./HandIcon');
15 | const CallQuality = require('./CallQuality');
16 | 
17 | 
18 | const ConferenceDrawerParticipant = (props) => {
19 |     let [active, setActive] = useState(false);
20 |     let [speech, setSpeech] = useState(null);
21 |     const streams = props.participant.streams;
22 | 
23 |     React.useEffect(() => {
24 |         return () => {
25 |             if (speech !== null) {
26 |                 speech.stop();
27 |                 setSpeech(null);
28 |             }
29 |         };
30 |     },[speech]);
31 | 
32 |     if (speech === null && props.enableSpeakingIndication && streams.length > 0 && streams[0].getAudioTracks().length !== 0) {
33 |         const options = {
34 |             interval: 150,
35 |             play: false
36 |         };
37 | 
38 |         const speechEvents = hark(streams[0], options);
39 |         speechEvents.on('speaking', () => {
40 |             setActive(true);
41 |         });
42 |         speechEvents.on('stopped_speaking', () => {
43 |             setActive(false);
44 |         });
45 |         setSpeech(speechEvents);
46 |     }
47 | 
48 |     let tag = '';
49 |     let callQuality;
50 |     if (props.isLocal) {
51 |         tag = Myself ;
52 |     } else {
53 |         if (props.stats) {
54 |             callQuality = (
59 |             
60 |                  
62 |             
63 |                 {props.participant.identity.displayName || props.participant.identity.uri} {callQuality} 
64 |              
65 |             
66 |                  props.handleHandSelected(props.participant)}
69 |                     disableHandToggle={props.disableHandToggle}
70 |                     drawer
71 |                 />
72 |                 {tag}
73 |               
74 |          
75 |     );
76 | 
77 | }
78 | 
79 | ConferenceDrawerParticipant.propTypes = {
80 |     participant: PropTypes.object.isRequired,
81 |     raisedHand: PropTypes.number.isRequired,
82 |     handleHandSelected: PropTypes.func.isRequired,
83 |     disableHandToggle: PropTypes.bool,
84 |     isLocal: PropTypes.bool,
85 |     enableSpeakingIndication: PropTypes.bool,
86 |     stats: PropTypes.object
87 | };
88 | 
89 | 
90 | module.exports = ConferenceDrawerParticipant;
91 | 
--------------------------------------------------------------------------------
/src/app/components/ConferenceDrawerParticipantList.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React             = require('react');
 4 | const PropTypes         = require('prop-types');
 5 | const ReactBootstrap    = require('react-bootstrap');
 6 | const ListGroup         = ReactBootstrap.ListGroup;
 7 | const ListGroupItem     = ReactBootstrap.ListGroupItem;
 8 | 
 9 | 
10 | const ConferenceDrawerParticipantList = (props) => {
11 |     const items = [];
12 |     let idx = 0;
13 |     React.Children.forEach(props.children, (child) => {
14 |         items.push({child} );
15 |         idx++;
16 |     });
17 | 
18 |     return (
19 |         
20 |             
Participants 
21 |             
22 |                 {items}
23 |              
24 |         
25 |     );
26 | };
27 | 
28 | ConferenceDrawerParticipantList.propTypes = {
29 |     children: PropTypes.node
30 | };
31 | 
32 | 
33 | module.exports = ConferenceDrawerParticipantList;
34 | 
--------------------------------------------------------------------------------
/src/app/components/ConferenceHeader.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React             = require('react');
  4 | const useState          = React.useState;
  5 | const useEffect         = React.useEffect;
  6 | const useRef            = React.useRef;
  7 | const PropTypes         = require('prop-types');
  8 | const { default: clsx } = require('clsx');
  9 | const { default: TransitionGroup } = require('react-transition-group/TransitionGroup');
 10 | const { default: CSSTransition } = require('react-transition-group/CSSTransition');
 11 | const { Duration }      = require('luxon');
 12 | 
 13 | const useInterval = (callback, delay) => {
 14 |     const savedCallback = useRef();
 15 | 
 16 |     // Remember the latest callback.
 17 |     useEffect(() => {
 18 |         savedCallback.current = callback;
 19 |     }, [callback]);
 20 | 
 21 |     // Set up the interval.
 22 |     useEffect(() => {
 23 |         function tick() {
 24 |             savedCallback.current();
 25 |         }
 26 |         if (delay !== null) {
 27 |             let id = setInterval(tick, delay);
 28 |             return () => clearInterval(id);
 29 |         }
 30 |     }, [delay]);
 31 | }
 32 | 
 33 | const ConferenceHeader = (props) => {
 34 |     let [seconds, setSeconds] = useState(0);
 35 | 
 36 |     useInterval(() => {
 37 |         setSeconds(seconds + 1);
 38 |     }, 1000);
 39 | 
 40 |     const duration = Duration.fromObject({seconds: seconds}).toFormat('hh:mm:ss');
 41 | 
 42 |     let videoHeader;
 43 |     let callButtons;
 44 | 
 45 |     const mainClasses = clsx({
 46 |         'top-overlay': true,
 47 |         'on-top': props.onTop
 48 |     });
 49 |     if (props.show) {
 50 |         const participantCount = props.participants.length + 1;
 51 | 
 52 |         const callDetail = (
 53 |             
 54 |                  
 59 |         );
 60 | 
 61 |         let electron = false;
 62 |         if (typeof window.process !== 'undefined') {
 63 |             if (window.process.versions.electron !== '' && window.process.platform === 'darwin') {
 64 |                 electron = true;
 65 |             }
 66 |         }
 67 | 
 68 |         const leftButtonClasses = clsx({
 69 |             'conference-top-left-buttons': true,
 70 |             'electron-margin': electron
 71 |         });
 72 | 
 73 |         const headerClasses = clsx({
 74 |             'call-header': true,
 75 |             'solid-background': props.transparent === false
 76 |         });
 77 | 
 78 |         videoHeader = (
 79 |             
 84 |                 
 85 |                     
 86 |                         
 87 |                             {props.buttons.top.left}
 88 |                         
 89 |                         
Conference:  {props.remoteIdentity}
 90 |                         
{callDetail}
 91 |                         
 92 |                             {props.buttons.top.right}
 93 |                         
 94 | 
 95 |                     
 96 |                 
 
 98 |         );
 99 | 
100 |         callButtons = (
101 |             
106 |             
107 |                 {props.buttons.bottom}
108 |             
109 |              
110 |         );
111 |     }
112 | 
113 |     return (
114 |         
115 |             
116 |                 {videoHeader}
117 |                 {callButtons}
118 |              
119 |         
120 |     );
121 | }
122 | 
123 | ConferenceHeader.propTypes = {
124 |     show: PropTypes.bool.isRequired,
125 |     remoteIdentity: PropTypes.string.isRequired,
126 |     participants: PropTypes.array.isRequired,
127 |     buttons: PropTypes.object.isRequired,
128 |     transparent: PropTypes.bool,
129 |     callQuality: PropTypes.object,
130 |     onTop: PropTypes.bool
131 | };
132 | 
133 | 
134 | module.exports = ConferenceHeader;
135 | 
--------------------------------------------------------------------------------
/src/app/components/ConferenceMenu.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React          = require('react');
 4 | const PropTypes      = require('prop-types');
 5 | const { makeStyles } = require('@material-ui/core/styles');
 6 | const { Menu, MenuItem, ListItemIcon }   = require('@material-ui/core');
 7 | 
 8 | 
 9 | const styleSheet = makeStyles({
10 |     item: {
11 |         fontSize: '14px',
12 |         color: '#333',
13 |         minHeight: 0,
14 |         lineHeight: '20px'
15 |     },
16 |     icon: {
17 |         minWidth: '20px'
18 |      }
19 | });
20 | 
21 | const ConferenceMenu = (props) => {
22 |     const classes = styleSheet();
23 | 
24 |     const handleShortcut = (event) => {
25 |         props.toggleShortcuts();
26 |         props.close(event);
27 |     };
28 | 
29 |     const handleDevices = (event) => {
30 |         props.toggleDevices();
31 |         props.close(event);
32 |     };
33 |     return (
34 |         
35 |             
58 |         
59 |     );
60 | }
61 | 
62 | ConferenceMenu.propTypes = {
63 |     show: PropTypes.bool.isRequired,
64 |     close: PropTypes.func.isRequired,
65 |     anchor: PropTypes.object,
66 |     toggleShortcuts: PropTypes.func,
67 |     toggleDevices: PropTypes.func
68 | };
69 | 
70 | 
71 | module.exports = ConferenceMenu;
72 | 
--------------------------------------------------------------------------------
/src/app/components/ConferenceParticipantSelf.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React             = require('react');
  4 | const PropTypes         = require('prop-types');
  5 | const ReactBootstrap    = require('react-bootstrap');
  6 | const Tooltip           = ReactBootstrap.Tooltip;
  7 | const OverlayTrigger    = ReactBootstrap.OverlayTrigger;
  8 | const sylkrtc           = require('sylkrtc');
  9 | const hark              = require('hark');
 10 | const { default: clsx } = require('clsx');
 11 | const UserIcon = require('./UserIcon');
 12 | 
 13 | class ConferenceParticipantSelf extends React.Component {
 14 |     constructor(props) {
 15 |         super(props);
 16 |         this.state = {
 17 |             active: false,
 18 |             hasVideo: false,
 19 |             sharesScreen: false
 20 |         }
 21 |         this.speechEvents = null;
 22 |     }
 23 | 
 24 |     componentDidMount() {
 25 |         sylkrtc.utils.attachMediaStream(this.props.stream, this.refs.videoElement, {disableContextMenu: true, muted: true});
 26 | 
 27 |         // factor it out to a function to avoid lint warning about calling setState here
 28 |         this.attachSpeechEvents();
 29 |         this.refs.videoElement.onresize = (event) => {
 30 |             this.handleResize(event)
 31 |         };
 32 |         if (this.props.audioOnly) {
 33 |             this.props.stream
 34 |         }
 35 |     }
 36 | 
 37 |     handleResize(event) {
 38 |         const resolutions = [ '1280x720', '960x540', '640x480', '640x360', '480x270','320x180'];
 39 |         const videoResolution = event.target.videoWidth + 'x' + event.target.videoHeight;
 40 |         if (resolutions.indexOf(videoResolution) === -1) {
 41 |             this.setState({sharesScreen: true});
 42 |         } else {
 43 |             this.setState({sharesScreen: false});
 44 |         }
 45 |     }
 46 | 
 47 |     componentWillUnmount() {
 48 |         if (this.speechEvents !== null) {
 49 |             this.speechEvents.stop();
 50 |             this.speechEvents = null;
 51 |         }
 52 |     }
 53 | 
 54 |     attachSpeechEvents() {
 55 |         this.setState({hasVideo: this.props.stream.getVideoTracks().length > 0});
 56 | 
 57 |         const options = {
 58 |             interval: 150,
 59 |             play: false
 60 |         };
 61 |         this.speechEvents = hark(this.props.stream, options);
 62 |         this.speechEvents.on('speaking', () => {
 63 |             this.setState({active: true});
 64 |         });
 65 |         this.speechEvents.on('stopped_speaking', () => {
 66 |             this.setState({active: false});
 67 |         });
 68 |     }
 69 | 
 70 |     render() {
 71 |         if (this.props.stream == null) {
 72 |             return false;
 73 |         }
 74 | 
 75 |         const tooltip = (
 76 |             {this.props.identity.displayName || this.props.identity.uri} 
 77 |         );
 78 | 
 79 |         const classes = clsx({
 80 |             'mirror' : this.state.hasVideo && !this.state.sharesScreen && !this.props.generatedVideoTrack,
 81 |             'poster' : !this.state.hasVideo,
 82 |             'fit'    : this.state.hasVideo && this.state.sharesScreen,
 83 |             'conference-active' : this.state.active,
 84 |             'hide' : !this.state.hasVideo
 85 |         });
 86 | 
 87 |         let muteIcon
 88 |         if (this.props.audioMuted) {
 89 |             muteIcon = (
 90 |                 
 91 |                     
 93 |             );
 94 |         }
 95 | 
 96 |         return (
 97 |             
 98 |                 {muteIcon}
 99 |                 
100 |                     
101 |                         {(!this.state.hasVideo) && 
104 |                  
105 |             
43 |         {({ TransitionProps }) => (
44 |           
45 |             
46 |               
47 |                 
48 |                     {children}
49 |                  
50 |                
51 |              
52 |            
53 |         )}
54 |        
55 |   );
56 | }
57 | 
58 | CustomContentMenu.propTypes = {
59 |     anchorEl    : PropTypes.object,
60 |     open        : PropTypes.bool.isRequired,
61 |     children    : PropTypes.node.isRequired,
62 |     onClose     : PropTypes.func.isRequired,
63 |     keepMounted : PropTypes.bool
64 | };
65 | 
66 | 
67 | module.exports = CustomContentMenu
68 | 
--------------------------------------------------------------------------------
/src/app/components/DividerWithText.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | const React         = require('react');
 3 | const PropTypes     = require('prop-types');
 4 | const { Divider, Grid } = require('@material-ui/core');
 5 | 
 6 | 
 7 | const DividerWithText = ({ children }) => (
 8 |     
 9 |         
10 |              
12 |         {children} 
13 |         
14 |              
16 |      
17 | );
18 | 
19 | DividerWithText.propTypes = {
20 |     children: PropTypes.node
21 | };
22 | 
23 | 
24 | module.exports = DividerWithText;
25 | 
--------------------------------------------------------------------------------
/src/app/components/DragAndDrop.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React = require('react');
  4 | const PropTypes = require('prop-types');
  5 | 
  6 | 
  7 | class DragAndDrop extends React.Component {
  8 |     state = {
  9 |         drag: false
 10 |     }
 11 | 
 12 |     dropRef = React.createRef()
 13 | 
 14 |     handleDrag = (e) => {
 15 |         e.preventDefault();
 16 |         e.stopPropagation();
 17 |     }
 18 | 
 19 |     handleDragIn = (e) => {
 20 |         e.preventDefault();
 21 |         e.stopPropagation();
 22 |         this.dragCounter++;
 23 |         if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
 24 |             this.setState({ drag: true });
 25 |         }
 26 |     }
 27 | 
 28 |     handleDragOut = (e) => {
 29 |         e.preventDefault();
 30 |         e.stopPropagation();
 31 |         this.dragCounter--;
 32 |         if (this.dragCounter === 0) {
 33 |             this.setState({ drag: false });
 34 |         }
 35 |     }
 36 | 
 37 |     handleDrop = (e) => {
 38 |         e.preventDefault();
 39 |         e.stopPropagation();
 40 |         this.setState({ drag: false });
 41 |         if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
 42 |             this.props.handleDrop(e.dataTransfer.files);
 43 |             e.dataTransfer.clearData();
 44 |             this.dragCounter = 0;
 45 |         }
 46 |     }
 47 | 
 48 |     componentDidMount() {
 49 |         let div = this.dropRef.current;
 50 |         div.addEventListener('dragenter', this.handleDragIn);
 51 |         div.addEventListener('dragleave', this.handleDragOut);
 52 |         div.addEventListener('dragover', this.handleDrag);
 53 |         div.addEventListener('drop', this.handleDrop);
 54 |         this.dragCounter = 0;
 55 |     }
 56 | 
 57 |     componentWillUnmount() {
 58 |         let div = this.dropRef.current
 59 |         div.removeEventListener('dragenter', this.handleDragIn);
 60 |         div.removeEventListener('dragleave', this.handleDragOut);
 61 |         div.removeEventListener('dragover', this.handleDrag);
 62 |         div.removeEventListener('drop', this.handleDrop);
 63 |     }
 64 | 
 65 |     render() {
 66 |         return (
 67 |             
 71 |                 {
 72 |                     this.state.drag &&
 73 |                     
 85 |                         
 96 |                             
{this.props.title ? this.props.title : 'Drop files to share them to the conference'}
 97 |                         
 98 |                     
 99 |                 }
100 |                 {this.props.children}
101 |             
 46 |             To replicate messages on multiple devices you need the same private key on all of them.Export  and enter this code when prompted on your other device: );
 49 |     } else if (step === 1 && !exp) {
 50 |         return (
 51 |             To replicate messages on multiple devices you need the same private key on all of them. );
 54 |     }
 55 |     return (
 56 |         You have used messaging on more than one device. To decrypt your messages, you need the same private key on all your devicesthe other device , choose the menu option 'Export private key' Do you want to keep this key  ?
 59 |      );
 60 | }
 61 | 
 62 | // On the other devices, you'll need to enter the password that will be provided here upon export.< br/>
 63 | const EncryptionModal = (props) => {
 64 |     const classes = styleSheet();
 65 |     const [step, setStep] = React.useState(0);
 66 |     const [password, setPassword] = React.useState();
 67 | 
 68 |     React.useEffect(() => {
 69 |         if (props.show === true) {
 70 |             setStep(0);
 71 |             setPassword(Math.random().toString().substr(2, 6));
 72 |         } else {
 73 |             setStep(0);
 74 |             setPassword('')
 75 |         }
 76 |     }, [props.show]);
 77 | 
 78 |     return (
 79 |         
 87 |             
 88 |                 {props.export === false && step !== 1
 89 |                     ? 'Different key detected'
 90 |                     : 'Export private key'
 91 |                 }
 92 |              
 93 |             
 94 |                 
 95 |                     {getContent(props.export ? 1 : step, props.export)}
 96 |                     {(props.export || step === 1) && {password} }
 97 |                  
 98 |              
 99 |             
100 |                 {props.export === false && step !== 1 && (
101 |                      {
104 |                             setStep(1);
105 |                             props.useExistingKey(password);
106 |                         }}
107 |                         title="Keep this key"
108 |                     >
109 |                         Keep this key
110 |                      
111 |                     No 
112 |                  )}
113 |                 {props.export === true && (
114 |                      {
117 |                             props.exportKey(password);
118 |                         }}
119 |                         title="export"
120 |                     >
121 |                         Export
122 |                      
123 |                 )}
124 |                 {(step === 1 || props.export === true) && (
125 |                     Close 
126 |                 )}
127 |              
128 |          
129 |     );
130 | }
131 | 
132 | EncryptionModal.propTypes = {
133 |     show: PropTypes.bool.isRequired,
134 |     close: PropTypes.func.isRequired,
135 |     exportKey: PropTypes.func.isRequired,
136 |     useExistingKey: PropTypes.func.isRequired,
137 |     export: PropTypes.bool
138 | };
139 | 
140 | 
141 | module.exports = EncryptionModal;
142 | 
--------------------------------------------------------------------------------
/src/app/components/ErrorPanel.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React          = require('react');
 4 | const PropTypes      = require('prop-types');
 5 | const ReactBootstrap = require('react-bootstrap');
 6 | const Modal          = ReactBootstrap.Modal;
 7 | 
 8 | 
 9 | const ErrorPanel = (props) => {
10 |     return (
11 |         
12 |             
13 |                  
15 |             
16 |                 {props.errorMsg}
17 |              
18 |          
19 |     );
20 | }
21 | 
22 | ErrorPanel.propTypes = {
23 |     errorMsg: PropTypes.object.isRequired
24 | };
25 | 
26 | 
27 | module.exports = ErrorPanel;
28 | 
--------------------------------------------------------------------------------
/src/app/components/EscalateConferenceModal.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React          = require('react');
 4 | const PropTypes      = require('prop-types');
 5 | const ReactBootstrap = require('react-bootstrap');
 6 | const Modal          = ReactBootstrap.Modal;
 7 | 
 8 | const config          = require('../config');
 9 | 
10 | 
11 | class EscalateConferenceModal extends React.Component {
12 |     constructor(props) {
13 |         super(props);
14 |         this.invitees = React.createRef();
15 | 
16 |         this.escalate = this.escalate.bind(this);
17 |     }
18 | 
19 |     escalate(event) {
20 |         event.preventDefault();
21 |         const uris = [];
22 |         for (let item of this.invitees.current.value.split(',')) {
23 |             item = item.trim();
24 |             if (item.indexOf('@') === -1) {
25 |                 item = `${item}@${config.defaultDomain}`;
26 |             }
27 |             uris.push(item);
28 |         };
29 |         uris.push(this.props.call.remoteIdentity.uri);
30 |         this.props.escalateToConference(uris);
31 |     }
32 | 
33 |     render() {
34 |         return (
35 |             
36 |                 
37 |                     Move to conference 
38 |                  
39 |                 
40 |                     Please enter the account(s) you wish to add to this call. After pressing Move, all parties will be invited to join a conference.
41 |                     
52 |                  
53 |              
54 |         );
55 |     }
56 | }
57 | 
58 | EscalateConferenceModal.propTypes = {
59 |     show: PropTypes.bool.isRequired,
60 |     close: PropTypes.func.isRequired,
61 |     call: PropTypes.object,
62 |     escalateToConference: PropTypes.func
63 | };
64 | 
65 | 
66 | module.exports = EscalateConferenceModal;
67 | 
--------------------------------------------------------------------------------
/src/app/components/FooterBox.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React = require('react');    // no, you can't remove this
 4 | 
 5 | 
 6 | const FooterBox = () => {
 7 |     return (
 8 |         
15 |     );
16 | };
17 | 
18 | 
19 | module.exports = FooterBox;
20 | 
--------------------------------------------------------------------------------
/src/app/components/HandIcon.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React         = require('react');
 4 | const PropTypes     = require('prop-types');
 5 | 
 6 | const { makeStyles }    = require('@material-ui/core/styles');
 7 | const { Badge }         = require('@material-ui/core');
 8 | 
 9 | 
10 | const styleSheet = makeStyles({
11 |     badge: {
12 |         width: '20px',
13 |         height: '20px',
14 |         fontWeight: 'bold',
15 |         fontSize: '1rem',
16 |         backgroundColor: '#337ab7'
17 |     },
18 |     badgeDrawer: {
19 |         width: '20px',
20 |         height: '20px',
21 |         fontWeight: 'bold',
22 |         fontSize: '1rem',
23 |         backgroundColor: '#337ab7',
24 |         position: 'unset',
25 |         marginLeft: '-10px',
26 |         transform: 'none'
27 |     },
28 |     rootThumb: {
29 |         display: 'block',
30 |         bottom: '25px',
31 |         position: 'absolute',
32 |         zIndex: 3
33 |     }
34 | });
35 | 
36 | const HandIcon = (props) => {
37 |     let content = null;
38 |     const classes = styleSheet();
39 |     let badgeClass = classes.badge;
40 |     if (props.drawer) {
41 |         badgeClass = classes.badgeDrawer;
42 |     }
43 |     let rootClass;
44 |     if (props.thumb) {
45 |         rootClass = classes.rootThumb;
46 |     }
47 |     if (props.raisedHand !== -1) {
48 |         let button = (
49 |             
50 |                  
52 |         );
53 |         if (props.disableHandToggle) {
54 |             button = (
58 |                 {button}
59 |              
60 |         );
61 |     }
62 |     return (content);
63 | }
64 | 
65 | HandIcon.propTypes = {
66 |     raisedHand          : PropTypes.number.isRequired,
67 |     handleHandSelected  : PropTypes.func.isRequired,
68 |     disableHandToggle   : PropTypes.bool,
69 |     drawer              : PropTypes.bool,
70 |     thumb               : PropTypes.bool
71 | };
72 | 
73 | 
74 | module.exports = HandIcon;
75 | 
--------------------------------------------------------------------------------
/src/app/components/HistoryCard.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React = require('react');
  4 | const PropTypes = require('prop-types');
  5 | const { default: clsx } = require('clsx');
  6 | const { DateTime, Duration } = require('luxon');
  7 | 
  8 | const { makeStyles } = require('@material-ui/core/styles');
  9 | const { Card, CardActions, CardContent } = require('@material-ui/core');
 10 | const { Typography, IconButton: Button } = require('@material-ui/core');
 11 | const UserIcon = require('./UserIcon');
 12 | 
 13 | 
 14 | const styles = makeStyles({
 15 |     card: {
 16 |         display: 'flex'
 17 |     },
 18 |     content: {
 19 |         flex: '1 0 auto',
 20 |         textAlign: 'left',
 21 |         maxWidth: 'calc( 100vw - 132px )',
 22 |         paddingBottom: 0
 23 |     },
 24 |     icon: {
 25 |         margin: 'auto'
 26 |     },
 27 |     column: {
 28 |         display: 'flex',
 29 |         flex: '1 1 auto',
 30 |         flexDirection: 'column',
 31 |         minWidth: 0
 32 |     },
 33 |     biggerFont: {
 34 |         fontSize: '1.1rem'
 35 |     },
 36 |     actions: {
 37 |         paddingTop: 4
 38 |     },
 39 |     iconSmall: {
 40 |         width: 40,
 41 |         height: 40,
 42 |         color: '#337ab7'
 43 |     },
 44 |     mainHeading: {
 45 |         color: props => props.historyItem.direction === 'received' && props.historyItem.duration === 0 ? '#a94442' : 'inherit'
 46 |     }
 47 | });
 48 | 
 49 | const HistoryCard = (props) => {
 50 |     const classes = styles(props);
 51 |     const identity = {
 52 |         displayName: props.historyItem.displayName,
 53 |         uri: props.historyItem.remoteParty || props.historyItem
 54 |     }
 55 | 
 56 |     const directionIcon = clsx({
 57 |         'fa': true,
 58 |         'rotate-minus-45': true,
 59 |         'fa-long-arrow-left': props.historyItem.direction === 'received',
 60 |         'fa-long-arrow-right': props.historyItem.direction === 'placed'
 61 |     });
 62 | 
 63 |     const startVideoCall = (e) => {
 64 |         e.stopPropagation();
 65 |         if (props.noConnection === false) {
 66 |             props.setTargetUri(identity.uri);
 67 |             // We need to wait for targetURI
 68 |             setImmediate(() => {
 69 |                 props.startVideoCall(e);
 70 |             });
 71 |         }
 72 |     }
 73 | 
 74 |     const startAudioCall = (e) => {
 75 |         e.stopPropagation();
 76 |         props.setTargetUri(identity.uri);
 77 |         // We need to wait for targetURI
 78 |         setImmediate(() => {
 79 |             props.startAudioCall(e);
 80 |         });
 81 |     }
 82 | 
 83 |     const startChat = (e) => {
 84 |         e.stopPropagation();
 85 |         props.setTargetUri(identity.uri);
 86 |         // We need to wait for targetURI
 87 |         setImmediate(() => {
 88 |             props.startChat(e);
 89 |         });
 90 |     }
 91 | 
 92 |     let duration = Duration.fromObject({ seconds: props.historyItem.duration }).toFormat('hh:mm:ss');
 93 |     if (props.historyItem.direction === 'received' && props.historyItem.duration === 0) {
 94 |         duration = 'missed';
 95 |     }
 96 | 
 97 |     const name = identity.displayName || identity.uri;
 98 | 
 99 |     const date = DateTime.fromFormat(
100 |         `${props.historyItem.startTime} ${props.historyItem.timezone}`,
101 |         "yyyy-MM-dd' 'HH:mm:ss z"
102 |     ).toFormat('yyyy MM dd HH:mm:ss');
103 | 
104 |     return (
105 |          { props.setTargetUri(identity.uri) }}
108 |             onDoubleClick={startVideoCall}
109 |         >
110 |             
111 |                 
112 |                     {name} ({duration}) 
113 |                     
114 |                          
116 |                  
117 |                 
118 |                     
119 |                          
121 |                     
122 |                          
124 |                     
125 |                          
127 |                  
128 |             
129 |             
130 |                 
132 |          
133 |     );
134 | }
135 | 
136 | HistoryCard.propTypes = {
137 |     historyItem: PropTypes.object,
138 |     startAudioCall: PropTypes.func.isRequired,
139 |     startVideoCall: PropTypes.func.isRequired,
140 |     startChat: PropTypes.func.isRequired,
141 |     setTargetUri: PropTypes.func.isRequired,
142 |     noConnection: PropTypes.bool
143 | };
144 | 
145 | 
146 | module.exports = HistoryCard;
147 | 
--------------------------------------------------------------------------------
/src/app/components/HistoryTileBox.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React = require('react');
 4 | const PropTypes = require('prop-types');
 5 | 
 6 | const { Grid } = require('@material-ui/core');
 7 | 
 8 | 
 9 | const HistoryTileBox = (props) => {
10 |     return (
11 |         
12 |             
19 |                 {props.children}
20 |              
21 |         
22 |     );
23 | }
24 | 
25 | HistoryTileBox.propTypes = {
26 |     children: PropTypes.node
27 | };
28 | 
29 | 
30 | module.exports = HistoryTileBox;
31 | 
--------------------------------------------------------------------------------
/src/app/components/IncomingCallModal.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React          = require('react');
  4 | const useEffect      = React.useEffect;
  5 | const PropTypes      = require('prop-types');
  6 | const ReactBootstrap = require('react-bootstrap');
  7 | const Popover        = ReactBootstrap.Popover;
  8 | const OverlayTrigger = ReactBootstrap.OverlayTrigger;
  9 | 
 10 | const UserIcon       = require('./UserIcon');
 11 | 
 12 | 
 13 | const IncomingCallModal = (props) => {
 14 |     useEffect(() => {
 15 |         document.addEventListener('keyup', onKeyUp);
 16 |         return (() => {
 17 |             document.removeEventListener('keyup', onKeyUp);
 18 |         });
 19 |     });
 20 | 
 21 |     const onKeyUp = (event) => {
 22 |         switch (event.which) {
 23 |             case 27:
 24 |                 // ESC
 25 |                 props.onHangup()
 26 |                 break;
 27 |             default:
 28 |                 break;
 29 |         }
 30 |     };
 31 | 
 32 |     const answerAudioOnly = () => {
 33 |         props.onAnswer({audio: true, video: false});
 34 |     }
 35 | 
 36 |     const answer = () => {
 37 |         props.onAnswer({audio: true, video: true});
 38 |     };
 39 | 
 40 |     if (props.call == null) {
 41 |         return false;
 42 |     }
 43 | 
 44 |     const buttonText = ['Decline', 'Accept', 'Audio'];
 45 | 
 46 |     let answerButtons = [
 47 |         
 48 |              
 52 |     ];
 53 | 
 54 |     let callType = 'audio';
 55 |     if (props.call.mediaTypes.video) {
 56 |         callType = 'video';
 57 |         answerButtons.push(
 58 |              );
 62 |     }
 63 | 
 64 |     answerButtons.push(
 65 |          );
 69 | 
 70 |     const remoteIdentityLine = props.call.remoteIdentity.displayName || props.call.remoteIdentity.uri;
 71 | 
 72 |     const tooltip = (
 73 |         
 74 |             {props.call.remoteIdentity.uri} 
 75 |          
 76 |     );
 77 | 
 78 |     const spacers = [
 79 |         
 84 |             
 85 |             
 86 |                 
 87 |                     
 88 |                         
 89 |                              
 91 |                         
{remoteIdentityLine} 
 92 |                         
is calling with {callType} 
 93 |                         
 94 |                         {props.compact ? '' : spacers}
 95 |                         
 96 |                     
 97 |                 
 98 |             
 99 |         
38 |                 
39 |                     Invite Online Users 
40 |                  
41 |                 
42 |                     Enter the users you wish to invite
43 |                     
54 |                  
55 |              
56 |         );
57 |     }
58 | }
59 | 
60 | InviteParticipantsModal.propTypes = {
61 |     show: PropTypes.bool.isRequired,
62 |     close: PropTypes.func.isRequired,
63 |     call: PropTypes.object
64 | };
65 | 
66 | 
67 | module.exports = InviteParticipantsModal;
68 | 
--------------------------------------------------------------------------------
/src/app/components/ListWithStickyHeader.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React = require('react');
 4 | const PropTypes = require('prop-types');
 5 | 
 6 | const { useInView } = require('react-intersection-observer');
 7 | 
 8 | const { makeStyles } = require('@material-ui/core/styles');
 9 | 
10 | 
11 | const styleSheet = makeStyles((theme) => ({
12 |     sticky: {
13 |         position: 'sticky',
14 |         top: '-1px',
15 |         backgroundColor: 'rgba(230, 230, 230, .85)',
16 |         zIndex: 1
17 |     }
18 | }));
19 | 
20 | const ListWithStickyHeader = ({ children, header }) => {
21 |     const classes = styleSheet();
22 | 
23 |     const { ref, inView } = useInView({
24 |         threshold: 0
25 |     });
26 | 
27 |     const { ref: ref2, inView: inView2 } = useInView({
28 |         threshold: [1]
29 |     });
30 |     return (
31 |         
32 |             {header}
33 |             {children}
34 |          
35 |     )
36 | }
37 | 
38 | ListWithStickyHeader.propTypes = {
39 |     header: PropTypes.object,
40 |     children: PropTypes.object
41 | };
42 | 
43 | 
44 | module.exports = ListWithStickyHeader;
45 | 
--------------------------------------------------------------------------------
/src/app/components/LoadingScreen.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React = require('react');
 4 | const PropTypes  = require('prop-types');
 5 | const { default: clsx } = require('clsx');
 6 | 
 7 | const LoadingScreen = (props) => {
 8 |     const textDisplayClasses = clsx({
 9 |         'hidden': props.text.length === 0
10 |     });
11 | 
12 |     return (
13 |         
14 |             
15 |             
16 |                 
17 |                     
18 |                         
{props.text} 
20 |                     
21 |                 
22 |             
23 |         
51 |                 
56 |                 
57 |                 
58 |                       
59 |                 
60 |             
 {
 82 |                 if (reason !== 'backdropClick') {
 83 |                     props.close();
 84 |                 }
 85 |             }}
 86 |             maxWidth="sm"
 87 |             fullWidth={true}
 88 |             aria-labelledby="dialog-titile"
 89 |             aria-describedby="dialog-description"
 90 |             disableEscapeKeyDown
 91 |         >
 92 |             
 93 |                 Sign out of Sylk
 94 |                 
 95 |                      
 97 |              
 98 |             
 99 |                 
100 |                     You will be no longer reachable for calls and messages on this device/browser.
101 |                  
102 |              
103 |             
104 |                 
110 |                      
128 |                 Cancel 
129 |                  {props.logout(removeData); _setRemoveData(false)}} variant="contained" title="Sign Out">Sign Out 
130 |                  
131 |          
132 |     );
133 | }
134 | 
135 | LogoutModal.propTypes = {
136 |     show: PropTypes.bool.isRequired,
137 |     close: PropTypes.func.isRequired,
138 |     logout: PropTypes.func.isRequired
139 | };
140 | 
141 | 
142 | module.exports = LogoutModal;
143 | 
--------------------------------------------------------------------------------
/src/app/components/MessagesLoadingScreen.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React     = require('react');
 4 | const PropTypes = require('prop-types');
 5 | 
 6 | const { LinearProgress } = require('@material-ui/core');
 7 | 
 8 | 
 9 | const MessagesLoadingScreen = (props) => {
10 |     return (
11 |         
12 |         
13 |             
14 |                 
15 |                     
16 |                         
17 |                         {props.progress !== 'storing'
18 |                             ? 
Decrypting messages ... 
19 |                             : 
Processing messages ... 
20 |                         }
21 |                         
22 |                             
30 |                     
31 |                 
32 |             
33 |         
16 |             
17 |                 Mute audio from everybody except yourself? 
18 |              
19 |             
20 |                 You can mute the audio from everybody, but you can't unmute them.
21 |                 They can unmute themselves at any time.
22 |                 
23 |                     Mute 
24 |                     Cancel 
25 |                 
26 |              
27 |          
28 |     );
29 | }
30 | 
31 | MuteAudioParticipantsModal.propTypes = {
32 |     show: PropTypes.bool.isRequired,
33 |     close: PropTypes.func.isRequired,
34 |     handleMute: PropTypes.func.isRequired
35 | };
36 | 
37 | 
38 | module.exports = MuteAudioParticipantsModal;
39 | 
--------------------------------------------------------------------------------
/src/app/components/NewDeviceModal.js:
--------------------------------------------------------------------------------
 1 | 
 2 | 'use strict';
 3 | 
 4 | const React          = require('react');
 5 | const PropTypes      = require('prop-types');
 6 | const { makeStyles } = require('@material-ui/core/styles');
 7 | const { 
 8 |     Dialog, 
 9 |     DialogTitle, 
10 |     DialogContent, 
11 |     DialogContentText, 
12 |     DialogActions }  = require('@material-ui/core');
13 | const { Button }     = require('../MaterialUIAsBootstrap');
14 | 
15 | 
16 | const styleSheet = makeStyles({
17 |     bigger: {
18 |         '&> h2': {
19 |             fontSize: '20px'
20 |         },
21 |         '&> div > p ': {
22 |             fontSize: '14px'
23 |         },
24 |         '&> li.MuiListSubheader-root': {
25 |             fontSize: '14px',
26 |             textAlign: 'left'
27 |         }
28 |     },
29 |     fixFont: {
30 |         fontFamily: 'inherit',
31 |         fontSize: '14px',
32 |         textAlign: 'left'
33 |     }
34 | });
35 | 
36 | function getContent() {
37 |     return (
38 |         To decrypt your messages, your private key is required.'Export private key' lost access  to this device/browser, please continue with 'Generate a new private key',
41 |         or 'Cancel' and messaging will be disabled .
42 |         If you choose to generate a new key, your previous messages cannot be read on newer devices.
43 |      );
44 | }
45 | 
46 | const NewDeviceModal = (props) => {
47 |     const classes = styleSheet();
48 | 
49 |     return (
50 |          {
53 |                 if (reason !== 'backdropClick') {
54 |                     props.close();
55 |                 }
56 |             }}
57 |             maxWidth="sm"
58 |             fullWidth={true}
59 |             aria-labelledby="dialog-titile"
60 |             aria-describedby="dialog-description"
61 |             disableEscapeKeyDown
62 |         >
63 |         New device/browser? 
64 |                 
65 |                     
66 |                         {getContent()}
67 |                  
68 |              
69 |             
70 |                 Cancel 
71 |                 
77 |                     Generate Private Key
78 |                  
79 |              
80 |          
81 |     );
82 | }
83 | 
84 | NewDeviceModal.propTypes = {
85 |     show: PropTypes.bool.isRequired,
86 |     close: PropTypes.func.isRequired,
87 |     generatePGPKeys: PropTypes.func.isRequired,
88 |     private: PropTypes.bool.isRequired
89 | };
90 | 
91 | 
92 | module.exports = NewDeviceModal;
93 | 
--------------------------------------------------------------------------------
/src/app/components/PreMedia.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React = require('react');
  4 | const useRef = React.useRef;
  5 | const useEffect = React.useEffect;
  6 | const useState = React.useState;
  7 | const PropTypes  = require('prop-types');
  8 | const { default: clsx } = require('clsx');
  9 | 
 10 | const { default: CSSTransition } = require('react-transition-group/CSSTransition');
 11 | 
 12 | const { makeStyles }    = require('@material-ui/core/styles');
 13 | 
 14 | const sylkrtc               = require('sylkrtc');
 15 | 
 16 | 
 17 | const styleSheet = makeStyles({
 18 |     premediaOverlay: {
 19 |         position: 'absolute',
 20 |         top: 0,
 21 |         left: 0,
 22 |         width: '100%',
 23 |         height: '100%',
 24 |         background: 'linear-gradient(transparent, rgba(0,0,0,.9))'
 25 |     },
 26 |     hide: {
 27 |         opacity: 0,
 28 |         visibility: 'hidden'
 29 |     },
 30 |     background: {
 31 |         zIndex: -1
 32 |     }
 33 | });
 34 | 
 35 | const PreMedia = (props) => {
 36 |     const classes = styleSheet(props);
 37 |     const [show, setShow] = useState(false);
 38 |     const [init, setInit] = useState(false);
 39 |     const localVideo = useRef(null);
 40 | 
 41 |     useEffect(() => {
 42 |         if (localVideo.current !== null && props.localMedia) {
 43 |             if (props.localMedia.getVideoTracks().length !== 0) {
 44 |                 localVideo.current.addEventListener('playing', localVideoElementPlaying);
 45 |                 sylkrtc.utils.attachMediaStream(props.localMedia, localVideo.current, {disableContextMenu: true, muted: true});
 46 |             }
 47 |         }
 48 |         return (() => {
 49 |             if (localVideo.current !== null) {
 50 |                 localVideo.current.removeEventListener('playing', localVideoElementPlaying); //eslint-disable-line react-hooks/exhaustive-deps
 51 |             }
 52 |         })
 53 |     }, [props.localMedia]);
 54 | 
 55 |     useEffect(() => {
 56 |         if (localVideo.current !== null && props.hide) {
 57 |                 localVideo.current.removeEventListener('playing', localVideoElementPlaying);
 58 |                 setShow(false);
 59 |         }
 60 |     }, [props.hide]);
 61 | 
 62 |     const enter = () => {
 63 |         if (!init) {
 64 |             setInit(true);
 65 |         }
 66 |     };
 67 | 
 68 |     const localVideoElementPlaying = () => {
 69 |         setShow(true);
 70 |     };
 71 | 
 72 |     const videoClasses = clsx({
 73 |         'video-container'   : true
 74 |     },
 75 |         !init && classes.hide,
 76 |         classes.background
 77 |     );
 78 | 
 79 |     return (
 80 |         
 81 |             {props.localMedia &&
 82 |                 
 88 |                     
 92 |                  
 93 |             }
 94 |         
58 |             
59 |             
60 |                 
61 |                     
62 |                         
63 |                         
Please wait... 
64 |                         
 The connection has been lost. Attempting to resume the call.
65 |                             
66 |                                 Cancel resume 
74 |                             
75 |                     
76 |                 
77 |             
78 |         
37 |             Keyboard Shortcuts 
38 |             
39 |                 
40 |                     
41 |                         Mute or unmute your microphone 
42 |                         M  
44 |                     
45 |                         Mute or unmute your video 
46 |                         V  
48 |                     
49 |                         View or exit full screen 
50 |                         F  
52 |                     
53 |                         Switch between camera and screen sharing 
54 |                         S  
56 |                     
58 |                        Conferences:
59 |                      
60 |                     
61 |                         Open or close the chat 
62 |                         C  
64 |                     
65 |                         Open or close dialog to switch devices 
66 |                         D  
68 |                     
69 |                         Cycle through active speakers 
70 |                         space  
72 |                     
73 |                         Raise or lower your hand 
74 |                         H  
76 |                     
78 |                         Show help 
79 |                         ?  
81 |                 
82 | 
83 |             
84 |                 
85 |                     Close
86 |                  
87 |              
88 |          
89 |     );
90 | }
91 | 
92 | ShortcutsModal.propTypes = {
93 |     show: PropTypes.bool.isRequired,
94 |     close: PropTypes.func.isRequired
95 | };
96 | 
97 | 
98 | module.exports = ShortcutsModal;
99 | 
--------------------------------------------------------------------------------
/src/app/components/Statistics.js:
--------------------------------------------------------------------------------
  1 | 
  2 | 'use strict';
  3 | 
  4 | const React          = require('react');
  5 | const PropTypes      = require('prop-types');
  6 | 
  7 | const { Box } = require('@material-ui/core');
  8 | 
  9 | const {Tab, Tabs} = require('../MaterialUIAsBootstrap');
 10 | const Charts = require('./Statistics/Charts');
 11 | 
 12 | /* eslint-disable react/no-multi-comp */
 13 | const  TabPanel = (props) => {
 14 |     const { children, value, index } = props;
 15 |     return (
 16 |         
 22 |             {value === index && (
 23 |                 {children} 
 24 |             )}
 25 |         
 26 |     );
 27 | }
 28 | 
 29 | TabPanel.propTypes = {
 30 |       children: PropTypes.node,
 31 |       index: PropTypes.any.isRequired,
 32 |       value: PropTypes.any.isRequired
 33 | };
 34 | 
 35 | function a11yProps(index) {
 36 |     return {
 37 |         id: `tab-${index}`,
 38 |         'aria-controls': `tabpanel-${index}`
 39 |     };
 40 | }
 41 | 
 42 | const Statistics = ({
 43 |     videoData,
 44 |     audioData,
 45 |     lastData,
 46 |     videoElements,
 47 |     video,
 48 |     details
 49 | }) => {
 50 |     const [value, setValue] = React.useState(0);
 51 |     const videoGraphs = video !== undefined && video !== false;
 52 | 
 53 |     const handleChange = (event, newValue) => {
 54 |         setValue(newValue);
 55 |     };
 56 | 
 57 |     return (
 58 |         
 59 |         {videoGraphs ?
 60 |             
 61 |                 
 70 |                      
 73 |                 
 74 |                      
 82 |                 
 83 |                      
 88 |              
 89 |         :
 90 |             
 96 |     )
 97 | };
 98 | 
 99 | Statistics.propTypes = {
100 |     videoData: PropTypes.array,
101 |     audioData: PropTypes.array,
102 |     lastData: PropTypes.object,
103 |     videoElements: PropTypes.object,
104 |     video: PropTypes.bool,
105 |     details: PropTypes.bool
106 | };
107 | 
108 | 
109 | module.exports = Statistics;
110 | 
111 | 
--------------------------------------------------------------------------------
/src/app/components/Statistics/AreaGradientChart.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React          = require('react');
 4 | const PropTypes      = require('prop-types');
 5 | const {
 6 |     ResponsiveContainer,
 7 |     AreaChart,
 8 |     Area }          = require('recharts');
 9 | 
10 | 
11 | const AreaGradientGraph = ({
12 |     data,
13 |     dataKey,
14 |     height,
15 |     color
16 | }) => {
17 |     let stroke = '#2e6da4';
18 |     let fill = 'url(#colorBlue)';
19 | 
20 |     if (color && color === 'green') {
21 |         fill = 'url(#colorGreen)';
22 |         stroke = '#4cae4c';
23 |     }
24 |     if (!height) {
25 |         height = 60
26 |     }
27 | 
28 |     return (
29 |         
30 |             
36 |                 
37 |                     
38 |                          
41 |                     
42 |                          
45 |                  
46 |                  
55 |          
56 |     )
57 | };
58 | 
59 | AreaGradientGraph.propTypes = {
60 |     data: PropTypes.array.isRequired,
61 |     dataKey: PropTypes.string.isRequired,
62 |     height: PropTypes.number,
63 |     color: PropTypes.string
64 | };
65 | 
66 | 
67 | module.exports = AreaGradientGraph;
68 | 
69 | 
--------------------------------------------------------------------------------
/src/app/components/Statistics/LineChart.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React          = require('react');
 4 | const PropTypes      = require('prop-types');
 5 | 
 6 | const {
 7 |     ResponsiveContainer,
 8 |     LineChart,
 9 |     Line } = require('recharts');
10 | 
11 | const SylkLineChart = ({
12 |     data,
13 |     dataKey,
14 |     height,
15 |     type
16 | }) => {
17 |     if (!height) {
18 |         height = 60
19 |     }
20 |     if (!type) {
21 |         type = 'step';
22 |     }
23 | 
24 |     return (
25 |         
26 |             
32 |                  
40 |          
41 |     )
42 | };
43 | 
44 | SylkLineChart.propTypes = {
45 |     data: PropTypes.array.isRequired,
46 |     dataKey: PropTypes.string.isRequired,
47 |     height: PropTypes.number,
48 |     type: PropTypes.string
49 | };
50 | 
51 | 
52 | module.exports = SylkLineChart;
53 | 
54 | 
--------------------------------------------------------------------------------
/src/app/components/StatusBox.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React      = require('react');
 4 | const PropTypes  = require('prop-types');
 5 | const { default: clsx } = require('clsx');
 6 | 
 7 | 
 8 | const StatusBox = (props) => {
 9 |     const classes = clsx({
10 |         'alert' : true,
11 |         'alert-warning' : props.level === 'warning',
12 |         'alert-danger'  : props.level === 'danger',
13 |         'alert-info'    : props.level === 'info'
14 |     });
15 | 
16 |     const widthClasses = clsx({
17 |         'form-signin' : props.width === 'small' || !props.width,
18 |         'form-dial'   : props.width === 'medium',
19 |         'half-width'   : props.width === 'large'
20 |     });
21 | 
22 |     let message;
23 |     if (props.title) {
24 |         message = ({props.title} 
);
25 |     } else {
26 |         message = ({props.message} 
);
27 |     }
28 | 
29 |     return (
30 |         
31 |             
32 |                 {message}
33 |             
34 |         
39 |             {props.selected &&
40 |                 
41 |                      
43 |             }
44 |             {props.label}
45 |             {props.stream && !failed() &&
46 |                 
47 |             }
48 |          
49 |     );
50 | }
51 | 
52 | AudioMenuItem.propTypes = {
53 |     stream: PropTypes.any,
54 |     selected: PropTypes.bool,
55 |     label: PropTypes.string
56 | };
57 | 
58 | 
59 | module.exports = AudioMenuItem;
60 | 
--------------------------------------------------------------------------------
/src/app/components/SwitchDevicesMenu/VideoMenuItem.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | const React          = require('react');
  4 | const {useEffect, useRef}          = React;
  5 | const PropTypes      = require('prop-types');
  6 | const { makeStyles } = require('@material-ui/core/styles');
  7 | const { Fade, CircularProgress } = require('@material-ui/core');
  8 | 
  9 | const sylkrtc               = require('sylkrtc');
 10 | 
 11 | const { default: clsx }     = require('clsx');
 12 | 
 13 | const styleSheet = makeStyles((theme) => ({
 14 |     menuVideoContainer: {
 15 |         width: '240px',
 16 |         height: '132px',
 17 |         position: 'relative'
 18 |     },
 19 |     scaleFit: {
 20 |         transform: 'scale(-1, 1) !important',
 21 |         width: '100%',
 22 |         height: '100%',
 23 |         objectFit: 'cover'
 24 |     },
 25 |     videoOverlay: {
 26 |         position: 'absolute',
 27 |         width: '100%',
 28 |         height: '100%',
 29 |         background: 'rgba(50,50,50,.6)',
 30 |         zIndex: 1
 31 |     },
 32 |     videoLabel: {
 33 |         width: '220px',
 34 |         fontSize: 14,
 35 |         zIndex: 2,
 36 |         position: 'absolute',
 37 |         color: '#fff',
 38 |         textAlign: 'center',
 39 |         padding: '8px',
 40 |         textOverflow: 'ellipsis'
 41 |     },
 42 |     pending: {
 43 |         alignItems: 'center',
 44 |         display: 'flex',
 45 |         height: '100%',
 46 |         justifyContent: 'center',
 47 |         position: 'absolute',
 48 |         width: '100%',
 49 |         zIndex: 2
 50 |     },
 51 |     failedText: {
 52 |         fontSize: 14
 53 |     },
 54 |     selectedBorder: {
 55 |         border: '3px solid #4cae4c'
 56 |     }
 57 | }));
 58 | 
 59 | const VideoMenuItem = (props) => {
 60 |     const classes = styleSheet();
 61 |     const video = useRef();
 62 | 
 63 |     useEffect(() => {
 64 |         if (props.stream !== 'failed') {
 65 |             sylkrtc.utils.attachMediaStream(props.stream, video.current, {muted: true, disableContextMenu: true, mirror: true});
 66 |         }
 67 |     }, [props.stream])
 68 | 
 69 |     const failed = () => {
 70 |         return props.stream && props.stream === 'failed'
 71 |     }
 72 | 
 73 |     return (
 74 |         
 75 |         { failed() &&
 76 |             
Camera unavailable
 77 |         }
 78 |         { props.stream && !failed() &&
 79 |             
{props.label}
 80 |         }
 81 |         { props.stream ? (
 82 |             
 83 |                 
 84 |                  
 86 |         ) : (
 87 |             
 88 |                 
 96 |                      
 98 |             
 99 |         )}
100 |         
17 |             {value === index && (
18 |                 
19 |                     {children}
20 |                 
21 |             )}
22 |         
107 |                 
120 |         );
121 | 
122 |     }
123 | }
124 | 
125 | URIInput.propTypes = {
126 |     defaultValue: PropTypes.string.isRequired,
127 |     data: PropTypes.array.isRequired,
128 |     autoFocus: PropTypes.bool.isRequired,
129 |     onChange: PropTypes.func.isRequired,
130 |     onSelect: PropTypes.func.isRequired,
131 |     placeholder : PropTypes.string
132 | };
133 | 
134 | 
135 | module.exports = URIInput;
136 | 
--------------------------------------------------------------------------------
/src/app/components/UserIcon.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React     = require('react');
 4 | const PropTypes = require('prop-types');
 5 | const utils     = require('../utils');
 6 | 
 7 | const { makeStyles }    = require('@material-ui/core/styles');
 8 | const { Avatar }        = require('@material-ui/core');
 9 | 
10 | const { default: clsx } = require('clsx');
11 | 
12 | 
13 | const styleSheet = makeStyles({
14 |     root: {
15 |         transition: 'box-shadow 0.3s'
16 |     },
17 |     drawerAvatar: {
18 |         fontFamily: 'Helvetica Neue ,Helvetica, Arial, sans-serif',
19 |         textTransform: 'uppercase'
20 |     },
21 |     margin: {
22 |         margin: '5px'
23 |     },
24 |     card: {
25 |         width: '70px',
26 |         height: '70px',
27 |         fontSize: '2.5rem',
28 |         margin: '10px'
29 |     },
30 |     chatContact: {
31 |         width: '50px',
32 |         height: '50px',
33 |         fontSize: '1.5625rem',
34 |         margin: 0
35 |     },
36 |     carousel: {
37 |         width: '80px',
38 |         height: '80px',
39 |         fontSize: '2.85rem',
40 |         margin: 'auto'
41 |     },
42 |     large: {
43 |         width: '144px',
44 |         height: '144px',
45 |         fontSize: '5rem',
46 |         margin: 'auto'
47 |     },
48 |     shadow: {
49 |         boxShadow: '0 0 2px 2px #999'
50 |     },
51 |     shadowSmall: {
52 |         boxShadow: '0 0 5px 2px #999'
53 |     }
54 | });
55 | 
56 | const UserIcon = (props) => {
57 |     const classes = styleSheet();
58 |     const name = props.identity.displayName || props.identity.uri;
59 |     let initials = name.split(' ', 2).map(x => x[0]).join('');
60 |     const color = utils.generateMaterialColor(props.identity.uri)['300'];
61 |     const avatarClasses = clsx(
62 |         classes.root,
63 |         classes.drawerAvatar,
64 |         {[`${classes.card}`]: props.card},
65 |         {[`${classes.chatContact}`]: props.chatContact},
66 |         {[`${classes.large}`]: props.large},
67 |         {[`${classes.shadow}`]: props.active},
68 |         {[`${classes.shadowSmall}`]: props.active && props.small},
69 |         {[`${classes.carousel}`]: props.carousel},
70 |         {[`${classes.margin}`]: props.small}
71 |     );
72 | 
73 |     if (props.identity.uri === 'anonymous@anonymous.invalid') {
74 |         initials = 
79 |             {initials}
80 |          
81 |     );
82 | };
83 | 
84 | UserIcon.propTypes = {
85 |     identity: PropTypes.object.isRequired,
86 |     large: PropTypes.bool,
87 |     card: PropTypes.bool,
88 |     carousel: PropTypes.bool,
89 |     small: PropTypes.bool,
90 |     active: PropTypes.bool,
91 |     chatContact: PropTypes.bool
92 | };
93 | 
94 | 
95 | module.exports = UserIcon;
96 | 
--------------------------------------------------------------------------------
/src/app/components/VolumeBar.js:
--------------------------------------------------------------------------------
 1 | 'use strict';
 2 | 
 3 | const React                 = require('react');
 4 | const PropTypes             = require('prop-types');
 5 | const hark                  = require('hark');
 6 | 
 7 | const { withStyles }     = require('@material-ui/core/styles');
 8 | const { green }          = require('@material-ui/core/colors');
 9 | const { LinearProgress } = require('@material-ui/core');
10 | 
11 | 
12 | const styleSheet = {
13 |     colorSecondary: {
14 |         backgroundColor: green[100]
15 |     },
16 |     barColorSecondary: {
17 |         backgroundColor: green[500]
18 |     },
19 |     root: {
20 |         height: '10px',
21 |         opacity: '0.7'
22 |     },
23 |     bar1Determinate: {
24 |         transition: 'transform 0.2s linear'
25 |     }
26 | };
27 | 
28 | class VolumeBar extends React.Component {
29 | 
30 |   constructor(props) {
31 |         super(props);
32 |         this.speechEvents = null;
33 |         this.state = {
34 |             volume: 0
35 |         }
36 |     }
37 | 
38 |     componentDidMount() {
39 |         const options = {
40 |             interval: 225,
41 |             play: false
42 |         };
43 |         this.speechEvents = hark(this.props.localMedia, options);
44 |         this.speechEvents.on('volume_change', (vol, threshold) => {
45 |             this.setState({volume: 2 * (vol + 75)});
46 |         });
47 |     }
48 | 
49 |     componentDidUpdate(prevProps) {
50 |         if (prevProps.localMedia !== this.props.localMedia) {
51 |             if (this.speechEvents !== null) {
52 |                 this.speechEvents.stop();
53 |                 this.speechEvents = null;
54 |             }
55 |             const options = {
56 |                 interval: 225,
57 |                 play: false
58 |             };
59 |             this.speechEvents = hark(this.props.localMedia, options);
60 |             this.speechEvents.on('volume_change', (vol, threshold) => {
61 |                 this.setState({volume: 2 * (vol + 75)});
62 |             });
63 |         }
64 |     }
65 | 
66 |     componentWillUnmount() {
67 |         if (this.speechEvents !== null) {
68 |             this.speechEvents.stop();
69 |             this.speechEvents = null;
70 |         }
71 |     }
72 | 
73 |     render() {
74 |         let color = 'primary';
75 |         if (this.state.volume > 20) {
76 |             color = 'secondary';
77 |         }
78 |         return (
79 |             
107 |             
108 |                 
109 |                     
110 |                         {isPlaying ?
111 |                              
116 |                  
117 |                 
118 |                     
119 |                         
120 |                             
121 |                          
122 |                         
123 |                             {formatTime(currentTime)}
124 |                          
125 |                      
126 |                  
127 |              
128 |             {!ready && 
}
129 |         
 5 |              
 7 |         Sylk 
 9 |         
20 |             
21 |                 
22 |                     
23 |                         
24 |                     
25 |                     
26 |                         
32 |                         
33 |                             
34 |                             
35 |                                 
36 |                                     
37 |                                         
40 |                                         
41 |                                             This app works only with a JavaScript enabled browser.
42 |                                         
43 |                                 
44 |                             
45 |                         
 
47 |                 
48 |             
49 |         
 5 |              
 7 |         Sylk 
 9 |         
21 |             
22 |                 
23 |                     
24 |                         
25 |                     
26 |                     
27 |                         
33 |                         
34 |                             
35 |                             
36 |                                 
37 |                                     
38 |                                         
41 |                                         
42 |                                             This app works only with a JavaScript enabled browser.
43 |                                         
44 |                                 
45 |                             
46 |                         
 
48 |                 
49 |             
50 |