Improve connection making

This commit is contained in:
Thomas Nordquist
2019-01-08 02:21:55 +01:00
parent e72696dc57
commit bb0f96028d
3 changed files with 89 additions and 28 deletions

View File

@@ -1,9 +1,14 @@
import * as React from 'react' import * as React from 'react'
import { Typography, Toolbar, Modal, MenuItem, Button, Grid, Paper, TextField, Switch, FormControlLabel } from '@material-ui/core' import {
Typography, Toolbar, Modal,
MenuItem, Button, Grid, Paper,
TextField, Switch, FormControlLabel,
CircularProgress,
} from '@material-ui/core'
import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles' import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
import Notification from './Notification' import Notification from './Notification'
import { MqttOptions, DataSourceState } from '../../../../backend/src/DataSource' import { MqttOptions, DataSourceState } from '../../../../backend/src/DataSource'
import { addMqttConnectionEvent, makeConnectionStateEvent, rendererEvents } from '../../../../events' import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../../events'
import sha1 = require('sha1') import sha1 = require('sha1')
interface Props { interface Props {
@@ -14,18 +19,20 @@ interface Props {
} }
const protocols = [ const protocols = [
'tcp://', 'mqtt://',
'ws://', 'ws://',
] ]
interface State { interface State {
connecting: boolean
connectionId?: string
error?: Error error?: Error
visible: boolean visible: boolean
host: string host: string
protocol: string protocol: string
port: number port: number
ssl: boolean tls: boolean
sslValidation: boolean certValidation: boolean
clientId: string clientId: string
username: string username: string
password: string password: string
@@ -49,11 +56,13 @@ class Connection extends React.Component<Props, State> {
host: 'nodered', host: 'nodered',
protocol: protocols[0], protocol: protocols[0],
port: 1883, port: 1883,
ssl: false, tls: false,
sslValidation: true, certValidation: true,
clientId: '', clientId: '',
username: '', username: '',
password: '', password: '',
connecting: false,
connectionId: undefined,
} }
this.state = Object.assign({}, defaultState, storedSettings) this.state = Object.assign({}, defaultState, storedSettings)
@@ -71,16 +80,24 @@ class Connection extends React.Component<Props, State> {
url, url,
username: this.state.username || undefined, username: this.state.username || undefined,
password: this.state.username || undefined, password: this.state.username || undefined,
ssl: this.state.ssl, tls: this.state.tls,
sslValidation: this.state.sslValidation, certValidation: this.state.certValidation,
} }
} }
private connect() { private connect() {
this.setState({
connecting: true,
})
const options = this.optionsFromState() const options = this.optionsFromState()
const connectionId = (sha1(Math.random() + JSON.stringify(options)).slice(0, 8)) as string const connectionId = (sha1(Math.random() + JSON.stringify(options)).slice(0, 8)) as string
this.setState({ connectionId })
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId }) rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
rendererEvents.subscribe(makeConnectionStateEvent(connectionId), (state: DataSourceState) => { const event = makeConnectionStateEvent(connectionId)
rendererEvents.subscribe(event, (state: DataSourceState) => {
console.log(state)
if (state.connected) { if (state.connected) {
this.props.onConnection(connectionId) this.props.onConnection(connectionId)
this.setState({ visible: false }) this.setState({ visible: false })
@@ -91,6 +108,13 @@ class Connection extends React.Component<Props, State> {
}) })
} }
private disconnect() {
this.setState({
connecting: false,
})
rendererEvents.emit(removeConnection, this.state.connectionId)
}
public static styles: StyleRulesCallback<string> = (theme: Theme) => { public static styles: StyleRulesCallback<string> = (theme: Theme) => {
return { return {
root: { root: {
@@ -130,6 +154,7 @@ class Connection extends React.Component<Props, State> {
public render() { public render() {
const { classes } = this.props const { classes } = this.props
return <Modal open={this.state.visible} disableAutoFocus={true} onClose={() => { console.log('close') }}> return <Modal open={this.state.visible} disableAutoFocus={true} onClose={() => { console.log('close') }}>
<Paper className={classes.root}> <Paper className={classes.root}>
<Toolbar> <Toolbar>
@@ -190,13 +215,13 @@ class Connection extends React.Component<Props, State> {
margin="normal" margin="normal"
/> />
</Grid> </Grid>
<Grid item xs={3}> <Grid item xs={4}>
<div className={classes.switch}> <div className={classes.switch}>
<FormControlLabel <FormControlLabel
control={( control={(
<Switch <Switch
checked={this.state.sslValidation} checked={this.state.certValidation}
onChange={() => this.setState({ sslValidation: !this.state.sslValidation })} onChange={() => this.setState({ certValidation: !this.state.certValidation })}
color="primary" color="primary"
/> />
)} )}
@@ -205,36 +230,55 @@ class Connection extends React.Component<Props, State> {
/> />
</div> </div>
</Grid> </Grid>
<Grid item xs={3}> <Grid item xs={4}>
<div className={classes.switch}> <div className={classes.switch}>
<FormControlLabel <FormControlLabel
control={( control={(
<Switch <Switch
checked={this.state.ssl} checked={this.state.tls}
onChange={() => this.setState({ ssl: !this.state.ssl })} onChange={() => this.setState({ tls: !this.state.tls })}
color="primary" color="primary"
/> />
)} )}
label="Encryption" label="Encryption (tls)"
labelPlacement="bottom" labelPlacement="bottom"
/> />
</div> </div>
</Grid> </Grid>
<Grid item xs={6}></Grid> <Grid item xs={4}></Grid>
</Grid> </Grid>
<br /> <br />
<div style={{ textAlign: 'right' }}> <div style={{ textAlign: 'right' }}>
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}> <Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}>
Save Save
</Button> </Button>
<Button variant="contained" color="primary" className={classes.button} onClick={() => this.connect()}> { this.renderConnectButton() }
Connect
</Button>
</div> </div>
</form> </form>
</Paper> </Paper>
</Modal> </Modal>
} }
private renderConnectButton() {
const { classes } = this.props
if (this.state.connecting) {
return <Button variant="contained" color="primary" className={classes.button} onClick={this.onClickAbort}>
<CircularProgress size={22} style={{ marginRight: '10px' }} color="secondary" /> Abort
</Button>
}
return <Button variant="contained" color="primary" className={classes.button} onClick={this.onClickConnect}>
Connect
</Button>
}
private onClickConnect = () => {
this.connect()
}
private onClickAbort = () => {
this.disconnect()
}
} }
export default withStyles(Connection.styles, { withTheme: true })(Connection) export default withStyles(Connection.styles, { withTheme: true })(Connection)

View File

@@ -1,12 +1,13 @@
import { Client, connect as mqttConnect } from 'mqtt' import { Client, connect as mqttConnect } from 'mqtt'
import { DataSource, DataSourceStateMachine } from './' import { DataSource, DataSourceStateMachine } from './'
import * as Url from 'url'
export interface MqttOptions { export interface MqttOptions {
url: string url: string
username?: string username?: string
password?: string password?: string
ssl: boolean tls: boolean
sslValidation: boolean certValidation: boolean
} }
export class MqttSource implements DataSource<MqttOptions> { export class MqttSource implements DataSource<MqttOptions> {
@@ -22,13 +23,25 @@ export class MqttSource implements DataSource<MqttOptions> {
public connect(options: MqttOptions): DataSourceStateMachine { public connect(options: MqttOptions): DataSourceStateMachine {
this.stateMachine.setConnecting() this.stateMachine.setConnecting()
const client = mqttConnect(options.url, {
const urlStr = options.tls ? options.url.replace(/^(mqtt|ws):/, '$1s:') : options.url
let url
try {
url = Url.parse(urlStr)
} catch (error) {
this.stateMachine.setError(error)
throw error
}
const client = mqttConnect(url, {
resubscribe: false, resubscribe: false,
rejectUnauthorized: !options.certValidation,
}) })
this.client = client this.client = client
client.on('error', (error: Error) => { client.on('error', (error: Error) => {
console.log(error)
this.stateMachine.setError(error) this.stateMachine.setError(error)
}) })

View File

@@ -1,4 +1,8 @@
import { addMqttConnectionEvent, backendEvents, makeConnectionStateEvent, makeConnectionMessageEvent, AddMqttConnection } from '../../events' import {
addMqttConnectionEvent, backendEvents,
makeConnectionStateEvent, removeConnection,
makeConnectionMessageEvent, AddMqttConnection
} from '../../events'
import { MqttSource, DataSource } from './DataSource' import { MqttSource, DataSource } from './DataSource'
class ConnectionManager { class ConnectionManager {
@@ -6,17 +10,18 @@ class ConnectionManager {
public manageConnections() { public manageConnections() {
backendEvents.subscribe(addMqttConnectionEvent, this.handleConnectionRequest) backendEvents.subscribe(addMqttConnectionEvent, this.handleConnectionRequest)
backendEvents.subscribe(removeConnection, (connectionId) => this.removeConnection(connectionId))
} }
private handleConnectionRequest = (event: AddMqttConnection) => { private handleConnectionRequest = (event: AddMqttConnection) => {
console.log(event)
const connectionId = event.id const connectionId = event.id
const options = event.options const options = event.options
const connection = new MqttSource() const connection = new MqttSource()
this.connections[connectionId] = connection this.connections[connectionId] = connection
const connectionStateEvent = makeConnectionStateEvent(connectionId)
connection.stateMachine.onUpdate.subscribe((state) => { connection.stateMachine.onUpdate.subscribe((state) => {
backendEvents.emit(makeConnectionStateEvent(connectionId), state) backendEvents.emit(connectionStateEvent, state)
}) })
connection.connect(options) connection.connect(options)
@@ -36,7 +41,6 @@ class ConnectionManager {
public removeConnection(hash: string) { public removeConnection(hash: string) {
const connection = this.connections[hash] const connection = this.connections[hash]
connection.stateMachine
connection.disconnect() connection.disconnect()
delete this.connections[hash] delete this.connections[hash]
} }