Skip to content

Commit

Permalink
Simplifies server code, gladly. (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
skellock committed Aug 13, 2016
1 parent c53e1f7 commit 0b7ac42
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 212 deletions.
51 changes: 2 additions & 49 deletions packages/reactotron-app/App/Stores/SessionStore.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,16 @@
import UiStore from './UiStore'
import { ipcRenderer } from 'electron'
import { createServer } from 'reactotron-core-server'

class Session {

ui
bridgeTransport
server

constructor (port = 9090) {
// configure the bridge transport
this.bridgeTransport = {
stop: () => ipcRenderer.send('reactotron.stop'),
send: (command, commandPayload) => ipcRenderer.send('reactotron.send', command, commandPayload),
close: () => ipcRenderer.send('reactotron.close')
}

// javascript a few functions ... :(
let realConnect
let realDisconnect
let realCommand

// our own core-server transport
const createTransport = () => ({ port, onConnect, onDisconnect, onCommand }) => {
// where we grab a ref to the functions we'll callback
realConnect = onConnect
realDisconnect = onDisconnect
realCommand = onCommand

// but return the configured bridge
return this.bridgeTransport
}

// when the main process tells us it's ready
ipcRenderer.on('reactotron.initialized', () => {
// listen for connect messages from the main process & pipe them into our transport
ipcRenderer.on('reactotron.connect', (event, { id, address }) => {
realConnect && realConnect({ id, address })
})
// same for disconnect
ipcRenderer.on('reactotron.disconnect', (event, id) => {
realDisconnect && realDisconnect(id)
})
// and command
ipcRenderer.on('reactotron.command', (event, command, commandPayload) => {
realCommand && realCommand(command, commandPayload)
})

// let's tell the main process it should create the connection
ipcRenderer.send('reactotron.create', port)
})

// let's create our faux server & start it up
this.server = createServer({ port }, createTransport())
this.server = createServer({ port })
this.server.start()

// create the ui store
this.ui = new UiStore(this.server)
}

}

export default Session
44 changes: 0 additions & 44 deletions packages/reactotron-app/TransportBridge.js

This file was deleted.

5 changes: 1 addition & 4 deletions packages/reactotron-app/main.development.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { app, BrowserWindow, Menu, shell, ipcMain } from 'electron'
import createBridge from './TransportBridge'
import { app, BrowserWindow, Menu, shell } from 'electron'

let menu
let template
let mainWindow = null
let server = null

if (process.env.NODE_ENV === 'development') {
require('electron-debug')()
Expand All @@ -28,7 +26,6 @@ app.on('ready', () => {
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.show()
mainWindow.focus()
server = createBridge(mainWindow.webContents)
})

mainWindow.on('closed', () => {
Expand Down
1 change: 0 additions & 1 deletion packages/reactotron-core-client/scripts/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ client.configure({
shotgun()
},
onDisconnect: () => console.log('disconnected'),
onCommand: command => console.log({inbound: command}),
plugins: CorePlugins
})

Expand Down
129 changes: 67 additions & 62 deletions packages/reactotron-core-server/src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import R from 'ramda'
import { merge, length, find, propEq, without, contains, forEach, pluck, reject } from 'ramda'
import Commands from './commands'
import validate from './validation'
import { observable, computed, asFlat } from 'mobx'
import defaultTransport from './transport'
import socketIO from 'socket.io'

const DEFAULTS = {
port: 9090, // the port to live (required)
Expand All @@ -16,21 +16,12 @@ const DEFAULTS = {
class Server {

// the configuration options
@observable options = R.merge({}, DEFAULTS)
@observable options = merge({}, DEFAULTS)
started = false
messageId = 0
subscriptions = []

/**
* Holds the transport which bridges this server to a real socket. We need
* this for electron to proxy ipc between the main process & renderer.
*/
transport = null

/**
* The function which creates a transport. Called on start().
*/
createTransport
partialConnections = []
io

/**
* Holds the commands the client has sent.
Expand All @@ -46,81 +37,97 @@ class Server {
* How many people are connected?
*/
@computed get connectionCount () {
return R.length(this.connections)
return length(this.connections)
}

constructor (createTransport) {
if (createTransport) {
this.createTransport = createTransport
} else {
this.createTransport = defaultTransport
}
this.send = this.send.bind(this)
}

findConnectionById = connection => R.find(R.propEq('id', connection), this.connections)
findConnectionById = id => find(propEq('id', id), this.connections)
findPartialConnectionById = id => find(propEq('id', id), this.partialConnections)

/**
* Set the configuration options.
*/
configure (options = {}) {
// options get merged & validated before getting set
const newOptions = R.merge(this.options, options)
const newOptions = merge(this.options, options)
validate(newOptions)
this.options = newOptions
return this
}

/**
* Starts the server.
* Starts the server
*/
start () {
this.started = true
const { port, onStart } = this.options
const { onCommand, onConnect, onDisconnect } = this.options
let partialConnections = []

// start listening
this.transport = this.createTransport({
port,

// fires when we the transport receives a new connection
onConnect: ({id, address}) => {
const connection = { id, address }
partialConnections.push(connection)
onConnect && onConnect(connection)

// resend the subscriptions to the client upon connecting
this.stateValuesSendSubscriptions()
},

// fires when we say goodbye to someone
onDisconnect: id => {
const connection = this.findConnectionById(id)
this.connections.remove(connection)
onDisconnect && onDisconnect(connection)
},

// fires when the transport gives us a connection
onCommand: (id, {type, payload}) => {
this.io = socketIO(port)

// register events
this.io.on('connection', socket => {
// a wild client appears
const partialConnection = {
id: socket.id,
address: socket.request.connection.remoteAddress,
socket
}

// tuck them away in a "almost connected status"
this.partialConnections.push(partialConnection)

// trigger onConnect
onConnect(partialConnection)

// when this client disconnects
socket.on('disconnect', () => {
onDisconnect(socket.id)
// remove them from the list partial list
this.partialConnections = reject(propEq('id', socket.id), this.partialConnections)

// remove them from the main connections list
const severingConnection = find(propEq('id', socket.id), this.connections)
if (severingConnection) {
this.connections.remove(severingConnection)
onDisconnect && onDisconnect(severingConnection)
}
})

// when we receive a command from the client
socket.on('command', ({ type, payload }) => {
this.messageId++
const date = new Date()
const fullCommand = { type, payload, messageId: this.messageId, date }

// for client intros
if (type === 'client.intro') {
const partialConnection = R.find(R.propEq('id', id), partialConnections)
// find them in the partial connection list
const partialConnection = find(propEq('id', socket.id), this.partialConnections)

// add their address in
fullCommand.payload.address = partialConnection.address
partialConnections = R.without([partialConnection], partialConnections)

// remove them from the partial connections list
this.partialConnections = reject(propEq('id', socket.id), this.partialConnections)

// bestow the payload onto the connection
const connection = { ...partialConnection, ...payload }
const connection = merge(payload, { id: socket.id, address: partialConnection.address })

// then trigger the connection
this.connections.push(connection)
}

onCommand(fullCommand)
this.commands.addCommand(fullCommand)
}
onCommand(fullCommand)
})

// resend the subscriptions to the client upon connecting
this.stateValuesSendSubscriptions()
})

// trigger the start message
Expand All @@ -130,13 +137,13 @@ class Server {
}

/**
* Stops the server.
* Stops the server
*/
stop () {
const { onStop } = this.options
this.started = false
this.transport.stop()
this.transport.close()
forEach(s => s && s.connected && s.disconnect(), pluck('socket', this.connections))
this.io.close()

// trigger the stop message
onStop && onStop()
Expand All @@ -148,7 +155,7 @@ class Server {
* Sends a command to the client
*/
send (type, payload) {
this.transport.send('command', { type, payload })
this.io.sockets.emit('command', { type, payload })
}

/**
Expand Down Expand Up @@ -184,7 +191,7 @@ class Server {
*/
stateValuesSubscribe (path) {
// prevent duplicates
if (R.contains(path, this.subscriptions)) return
if (contains(path, this.subscriptions)) return
// subscribe
this.subscriptions.push(path)
this.stateValuesSendSubscriptions()
Expand All @@ -195,8 +202,8 @@ class Server {
*/
stateValuesUnsubscribe (path) {
// if it doesn't exist, jet
if (!R.contains(path, this.subscriptions)) return
this.subscriptions = R.without([path], this.subscriptions)
if (!contains(path, this.subscriptions)) return
this.subscriptions = without([path], this.subscriptions)
this.stateValuesSendSubscriptions()
}

Expand All @@ -213,10 +220,8 @@ class Server {
export default Server

// convenience factory function
export const createServer = (options, createTransport) => {
const server = new Server(createTransport)
export const createServer = (options) => {
const server = new Server()
server.configure(options)
return server
}

export const createDefaultTransport = defaultTransport
Loading

0 comments on commit 0b7ac42

Please sign in to comment.