Skip to content

Commit

Permalink
Merge pull request #17 from brendancboyle/dev
Browse files Browse the repository at this point in the history
v1.2.0
  • Loading branch information
bcbee authored Dec 26, 2018
2 parents a59f852 + 5a6001b commit e8680da
Show file tree
Hide file tree
Showing 19 changed files with 2,217 additions and 88 deletions.
3 changes: 3 additions & 0 deletions app/UPRKit/certificates/public.key.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkSUqAISX3Asy3bpqR91N\nO1lCwfwXOD+HsjECSpXqOn58YDRZDZkZXDI0RwUj9A2euq50tSb4/yxiex7SXC62\nlcZSirehHttBwy1130wytmJ1Tr1SDhMpw0GstorG8WXjAT/6LegB61/EGx93l/YB\nC54hZemS2qrMcUkMiTPzapbrYqKFrfy0Xe+Xi8NNPcRgqWbEo4OVRwpRIELPqyUd\nIqa5TibTquhdT48hYrRAE73yqAtElqKjnRgm0eDOxvH8YIRpeUNerJgRjCxmokqC\nIV8VnM2HnUepjV6NBm7/CjXB1ZboqpqJBn5ygDbxSlYk6Eq87qwUN1wMyDGR5Z13\nG0JqgYB8t4MdGMJ73MEPBzenCDRjSA33kmA2SZ+v3HGAXb/Zwmn0IoH1yE049eUJ\n80F7gkhVGj7w42Yw8vtcOFsJzMfMXR1SZV+XSana83HWUmeAqfpmAHHPH6T2Oqqn\n7YDb9aw/CYulB30lWcA39nvaPzvEwrYLH6Y/yRFEZQeVyofuyQDDxGnNghm4ICa8\n+IICV6ZdysflxRq6kASs4D8e3SvaEqOwkg1RXqIaCasDoMOWbCBccZNQbtB4GS/O\nDsGItTnVg8U1UgzzebSNG0s0eJ1WWphgL5da4uA8OpSsdhFQ3C+QAHmw4THsNwA6\n8UTTXcbxMb5UhMv2kQ38t2kCAwEAAQ==\n-----END PUBLIC KEY-----"
}
41 changes: 36 additions & 5 deletions app/UPRKit/utils.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,67 @@
import SendKeys from 'send-keys-native';
import io from 'socket.io-client';
import _ from 'lodash';
import crypto from 'crypto';
import * as Sentry from '@sentry/electron';
import pub from './certificates/public.key';

export const SLIDE_UP = 'SlideUp';
export const SLIDE_DOWN = 'SlideDown';
export const PLAY_MEDIA = 'PlayMedia';

let socket;
let sigCematary = {};

type messageType = {
action: string,
holdfor: string
holdfor: string,
signature: string,
timestamp: string
};

function listenForEvents(token: string, holdFor: string) {
socket = io('https://universalpresenterremote.com');
socket.on('error', e => {
Sentry.captureException(e);
});
socket.on(token, (message: messageType) => {
if (message.holdfor === holdFor) {
const validate: messageType = _.cloneDeep(message);
delete validate.signature;
const jsonMsg = JSON.stringify(validate);
const verify = crypto.createVerify('SHA256');
verify.write(jsonMsg);
verify.end();
const isValid = verify.verify(pub.key, message.signature, 'hex');
if (
isValid &&
message.holdfor === holdFor &&
!sigCematary[message.signature]
) {
sigCematary[message.signature] = new Date();
switch (message.action) {
case SLIDE_UP: {
SendKeys.rightArrow();
SendKeys.rightArrow().catch(e => {
Sentry.captureException(e);
});
break;
}
case SLIDE_DOWN: {
SendKeys.leftArrow();
SendKeys.leftArrow().catch(e => {
Sentry.captureException(e);
});
break;
}
case PLAY_MEDIA: {
SendKeys.rightArrow();
SendKeys.rightArrow().catch(e => {
Sentry.captureException(e);
});
break;
}
default:
break;
}
} else {
Sentry.captureMessage('Message signature mismatch.');
}
});
}
Expand All @@ -40,6 +70,7 @@ function disconnect() {
if (socket) {
socket.close();
socket = undefined;
sigCematary = {};
}
}

Expand Down
20 changes: 20 additions & 0 deletions app/components/Button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.button {
padding: 15px 0;
}

.button button {
width: 300px;
background: gray;
color: white;
font-size: 1.4rem;
padding: 10px 0;
border-radius: 5px;
border: none;
font-family: SugarcubesBold, Arial, 'Helvetica Neue', Helvetica, sans-serif;
outline-color: rgb(179, 2, 152);
}

.active button {
background: rgb(179, 2, 152);
color: white;
}
35 changes: 35 additions & 0 deletions app/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @flow
import React, { Component } from 'react';
import styles from './Button.css';

type Props = {
active: boolean,
title: string,
onClick: () => void
};

export default class Button extends Component<Props> {
render() {
const {
props: { active, title, onClick }
} = this;

const buttonStyles = [styles.button];
if (active) buttonStyles.push([styles.active]);

return (
<div className={buttonStyles.join(' ')}>
<button
type="button"
onClick={() => {
if (active) {
onClick();
}
}}
>
{title}
</button>
</div>
);
}
}
20 changes: 0 additions & 20 deletions app/components/ConnectButton.css
Original file line number Diff line number Diff line change
@@ -1,20 +0,0 @@
.connectButton {
padding: 15px 0;
}

.connectButton button {
width: 300px;
background: gray;
color: white;
font-size: 1.4rem;
padding: 10px 0;
border-radius: 5px;
border: none;
font-family: SugarcubesBold, Arial, 'Helvetica Neue', Helvetica, sans-serif;
outline-color: rgb(179, 2, 152);
}

.active button {
background: rgb(179, 2, 152);
color: white;
}
48 changes: 21 additions & 27 deletions app/components/ConnectButton.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// @flow
import React, { Component } from 'react';
import * as Sentry from '@sentry/electron';
import UPRKit from '../UPRKit';
import styles from './ConnectButton.css';
import Button from './Button';

const { dialog } = require('electron').remote;

Expand All @@ -20,28 +21,33 @@ type Props = {
}
};

export default class TokenForm extends Component<Props> {
props: Props;
export default class ConnectButton extends Component<Props> {
constructor(props) {
super(props);
this.joinSession = this.joinSession.bind(this);
}

async joinSession(event) {
async joinSession() {
const {
props: {
token,
history,
actions: { holdForActions, tokenActions }
}
} = this;
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = 'Joining...';
const status = await UPRKit.Session.joinSession(token);
const status = await UPRKit.Session.joinSession(token).catch(e => {
dialog.showErrorBox(
'Whoops!',
`There was an error connecting to UPR - (${e.message})`
);
Sentry.captureException(e);
});
if (!status) return;
if (status.data > 0) {
button.innerHTML = originalText;
holdForActions.setHoldFor(status.data.toString());
history.push('/present');
} else {
console.warn(status);
button.innerHTML = originalText;
dialog.showErrorBox(
'Whoops!',
'The token you entered does not appear to be valid.'
Expand All @@ -54,25 +60,13 @@ export default class TokenForm extends Component<Props> {
const {
props: { token }
} = this;
if (token.includes('_')) {
return (
<div className={[styles.connectButton].join(' ')}>
<button type="button">Enter a token...</button>
</div>
);
}

return (
<div className={[styles.connectButton, styles.active].join(' ')}>
<button
type="button"
onClick={e => {
this.joinSession(e);
}}
>
Start Presenting
</button>
</div>
<Button
active={!token.includes('_')}
title={token.includes('_') ? 'Enter a token...' : 'Start Presenting'}
onClick={this.joinSession}
/>
);
}
}
3 changes: 1 addition & 2 deletions app/components/Login.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
.container {
text-align: center;
position: absolute;
top: 5%;
width: 100%;
margin-top: 5%;
}

.container h2 {
Expand Down
85 changes: 74 additions & 11 deletions app/components/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,95 @@
// @flow
import React, { Component } from 'react';
import SendKeys from 'send-keys-native';
import * as Sentry from '@sentry/electron';
import styles from './Login.css';
import TokenForm from '../containers/TokenForm';
import ConnectButton from '../containers/ConnectButton';
import Logo from '../images/icon.png';
import Banner from '../images/banner.png';
import Modal from './Modal';

const { dialog } = require('electron').remote;

type Props = {
token: string
};

export default class Home extends Component<Props> {
props: Props;
type State = {
hasPermissions: boolean
};

export default class Login extends Component<Props, State> {
state = { hasPermissions: true };

componentDidMount() {
this.checkPermissions();
}

initialPermissionCheck = true;

checkPermissions() {
SendKeys.hasPermissions()
.then(v => {
if (!this.initialPermissionCheck && !v) {
dialog.showErrorBox(
'Whoops!',
`UPR is still missing the required permissions. Please try again, or restart UPR Desktop.`
);
Sentry.captureMessage('Check permissions failed', 'warning');
}
this.initialPermissionCheck = false;
return this.setState({ hasPermissions: v });
})
.catch(e => {
console.error(e);
});
}

macPermissionWarning() {
const {
state: { hasPermissions }
} = this;
if (hasPermissions) {
return null;
}
return (
<Modal
title="Welcome to UPR"
buttonTitle="Check Permissions"
buttonActive
buttonOnClick={() => {
this.checkPermissions();
}}
>
<div>
<h1>Review Permissions</h1>
<p>
In order for UPR to control your presentations, it needs permission
to and Application called System Events.
</p>
<p>
To allow this, open System Preferences then select Security &
Privacy. Make sure UPR is checked in the Automation tab.
</p>
</div>
</Modal>
);
}

render() {
const {
props: { token }
} = this;
return (
<div>
<div className={styles.container} data-tid="container">
<img src={Logo} alt="Logo" className={styles.logo} />
<br />
<img src={Banner} alt="Banner" className={styles.banner} />
<h2>Universal Presenter Remote</h2>
<TokenForm />
<ConnectButton token={token} />
</div>
<div className={styles.container} data-tid="container">
<img src={Logo} alt="Logo" className={styles.logo} />
<br />
<img src={Banner} alt="Banner" className={styles.banner} />
<h2>Universal Presenter Remote</h2>
<TokenForm />
<ConnectButton token={token} />
{this.macPermissionWarning()}
</div>
);
}
Expand Down
15 changes: 15 additions & 0 deletions app/components/Modal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.container {
position: absolute;
top: 0;
width: 75%;
height: 85%;
left: 12.5%;
background: #eee;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 0 10px grey;
}

.bodyContainer {
padding: 30px;
}
Loading

0 comments on commit e8680da

Please sign in to comment.