electron, flux + react

Post on 16-Apr-2017

2.113 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

Flux Data FlowUnidirectional data flow

Action Dispatcher Store View

Flux Data FlowUnidirectional data flow

Action Dispatcher Store View

Libs

DockMainCall

Node Process

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

top related