Improve connection making
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user