Download - Electron, Flux + React
Electron, Flux + ReactTom Moor (@tommoor)
What is Speak?Instant audio, video and screen share for remote teams
A Quick DemoWhat could go wrong?
App & Dock
Ring ring...
Audio & Video
Why Electron?!Are we sure this is a good idea...
Chromi ummmmCross platform full stack AV implementation
WebRTC as a first class citizen, always being improved upstream
NodeAccess to underlying system
SpeedLean startup, MVP
Known technologies, CSS & Javascript, NW.js
Cross platform a must for communication tools
Our ArchitectureIt’s events all the way down
MicroservicesRabbitMQ message bus
Distinct services for calls, authentication, websockets, audio mixing, api…
Mixture of Ruby and Go languages
Realtime APIEvents with payloads over websockets
Transactions for responses
The ClientElectron, Flux & React
RenderersMain, Dock, Call, (Preferences, Teams, Invites …)
Each renderer has it’s own React entry point
Each renderer has it’s own Flux Dispatcher and copy of all stores
Events are forwarded bi-directionally through IPC
Main RendererWe have one main renderer that communicates with server, renders audio etc
Because complex objects can’t be passed over IPC
Ensure this is never closed, only hidden
The lifetime of an eventdata flow with multiple renderers
User clicksdock renderer user-
menu.jsx
var React = require('react/addons');
var UserActions = require('../actions/user-actions');
var UserMenu = React.createClass({
call: function() {
UserActions.call(this.props.item);
},
render: function(){
return <ul className="actions user-actions">
<li key="call"><a onClick={this.call} className="positive"><i className="
icon-call"></i></a></li>
</ul>;
}
});
module.exports = UserMenu;
Event Dispatched
dock renderer
user-actions.js
var AppDispatcher = require('../dispatcher/app-dispatcher');
var UserStore = require('../stores/user-store');
var UserActions = {
call: function(user) {
AppDispatcher.dispatch('channel.invite', {
user_id: user.id,
sender_id: UserStore.get('id')
});
}
...
};
module.exports = UserActions;
IPC Senddock renderer
app-dispatcher.js
var ipc = require('ipc');
var browserWindow = require('remote').getCurrentWindow();
var AppDispatcher = Flux.createDispatcher({...});
// receive events from main process
ipc.on('event', function(action, payload, opt, browser_id) {
if (browser_id != browserWindow.id) {
AppDispatcher.dispatch(action, payload, opt, browser_id);
}
});
// send events from this renderer to main process
AppDispatcher.register(function(action, payload, opt, browser_id) {
if (!browser_id) {
ipc.send('event', action, payload, opt, browserWindow.id);
}
});
module.exports = AppDispatcher;
IPC Receivenode process
dispatcher.js
class AppDispatcher {
constructor(windows) {
ipc.on('event', function(ev, action, payload, opts, browser_id){
this.dispatch(action, payload, opts, browser_id);
}.bind(this));
}
dispatch(action, payload, opts, browser_id) {
browser_id = browser_id || 'node';
_.each(global.application.windows, function(window, window_id){
if (window_id != browser_id) {
window.browserWindow.webContents.send('event', action,
payload, opts, browser_id);
}
});
}
}
IPC Receivemain renderer
app-dispatcher.js
var ipc = require('ipc');
var browserWindow = require('remote').getCurrentWindow();
var AppDispatcher = Flux.createDispatcher({...});
// receive events from main process
ipc.on('event', function(action, payload, opt, browser_id) {
if (browser_id != browserWindow.id) {
AppDispatcher.dispatch(action, payload, opt, browser_id);
}
});
// send events from this renderer to main process
AppDispatcher.register(function(action, payload, opt, browser_id)
{
if (!browser_id) {
ipc.send('event', action, payload, opt, browserWindow.id);
}
});
module.exports = AppDispatcher;
WebSocketsmain renderer
socks.js
var Socks = {
actions: {
'channel.invite': 'send',
...
},
send: function(action, params, options) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
var data = {key: action};
if (params) data.params = params;
if (options) data.transaction_id = options;
this.ws.send(JSON.stringify(data));
return true;
}
}
};
module.exports = Socks;
What about new windows?Synchronising the state of the system
Local storage to the rescue!Automatically in sync between pages on the same domain
Extend the flux store
var Store = function(definition) {
return Flux.createStore(_.extend({
initialize: function() {
this.rehydrateStore();
this.onChange(function(){
LocalStorage.set(this.storeName, this.state);
}.bind(this));
},
rehydrateStore: function() {
var data = LocalStorage.get(this.storeName);
if (data) {
this.state = _.extend(this.state, data);
this.emit('change');
}
}
}, definition));
};
Using Delorean
ChallengesThink within the box
DesignWe come up with a lot of wild and wacky ideas…
Build on the advantages of Electron, not the disadvantages
Don’t dupe native styles and controls if possible
Complex objects limit designs (e.g streams)
PerformanceJavascript is FAST
IPC is slow, avoid at all costs
Starting windows is slow, we keep some open and hidden (hibernate?)
Each renderer and crash reporter is a process (it adds up quickly!)
Windows & LinuxCare of platform differences, conventions, wording
Chromium still renders css differently occasionally
Updates for each platform are quite different
Virtual machines, VMWare for developing on Windows
NPM3 for long file paths
Github for Windows recommended!
Questions?speak.io / @speak_io / @tommoor