Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Electron, Flux + React

Tom Moor
September 29, 2015

Electron, Flux + React

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

Tom Moor

September 29, 2015
Tweet

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. 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
  4. 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;
  5. 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;
  6. 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;
  7. IPC Receive node 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); } }); } }
  8. 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;
  9. 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;
  10. 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
  11. 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)
  12. 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!)
  13. 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!