How we built Speak (https://speak.io), a realtime team communication platform using an event sourced architecture and Electron, plus some of the challenges we faced.
Presented at the Electron Meetup at Github HQ on 29th September 2015: http://blog.atom.io/2015/09/17/electron-meetup.html
8. Chromi ummmm
Cross platform full stack AV implementation
WebRTC as a first class citizen, always being improved upstream
Node
Access to underlying system
Speed
Lean startup, MVP
Known technologies, CSS & Javascript, NW.js
Cross platform a must for communication tools
10. Microservices
RabbitMQ message bus
Distinct services for calls, authentication, websockets, audio mixing, api…
Mixture of Ruby and Go languages
Realtime API
Events with payloads over websockets
Transactions for responses
15. Renderers
Main, 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 Renderer
We 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
17. User clicks
dock 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;
18. 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;
19. IPC Send
dock 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;
21. IPC Receive
main 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;
22. WebSockets
main 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;
23. What about new windows?
Synchronising the state of the system
24. Local storage to the rescue!
Automatically in sync between pages on the same domain
25. 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
27. Design
We 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)
28. Performance
Javascript 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!)
29. Windows & Linux
Care 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!