Slide 1

Slide 1 text

Electron, Flux + React Tom Moor (@tommoor)

Slide 2

Slide 2 text

What is Speak? Instant audio, video and screen share for remote teams

Slide 3

Slide 3 text

A Quick Demo What could go wrong?

Slide 4

Slide 4 text

App & Dock

Slide 5

Slide 5 text

Ring ring...

Slide 6

Slide 6 text

Audio & Video

Slide 7

Slide 7 text

Why Electron?! Are we sure this is a good idea...

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Our Architecture It’s events all the way down

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

The Client Electron, Flux & React

Slide 12

Slide 12 text

Flux Data Flow Unidirectional data flow Action Dispatcher Store View

Slide 13

Slide 13 text

Flux Data Flow Unidirectional data flow Action Dispatcher Store View Libs

Slide 14

Slide 14 text

Dock Main Call Node Process

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

The lifetime of an event data flow with multiple renderers

Slide 17

Slide 17 text

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
; } }); module.exports = UserMenu;

Slide 18

Slide 18 text

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;

Slide 19

Slide 19 text

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;

Slide 20

Slide 20 text

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); } }); } }

Slide 21

Slide 21 text

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;

Slide 22

Slide 22 text

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;

Slide 23

Slide 23 text

What about new windows? Synchronising the state of the system

Slide 24

Slide 24 text

Local storage to the rescue! Automatically in sync between pages on the same domain

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Challenges Think within the box

Slide 27

Slide 27 text

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)

Slide 28

Slide 28 text

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!)

Slide 29

Slide 29 text

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!

Slide 30

Slide 30 text

Questions? speak.io / @speak_io / @tommoor