Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Building Realtime Apps with Ember.js and WebSockets

3,193 views

Published on

This talk discusses how AJAX differs from WebSockets and how the technology can be used to implement rich real-time experiences. It also produces a live demo using EmberJS.

Published in: Technology

Building Realtime Apps with Ember.js and WebSockets

  1. 1. Building Real-Time Apps with EmberJS & WebSockets
  2. 2. Ben LimmerGEMConf - 5/21/2016 ember.party blimmer
  3. 3. Ben LimmerEmberJS Meetup - 2/24/2016 ember.party ♥
  4. 4. Ben LimmerGEMConf - 5/21/2016 ember.party Talk Roadmap • WebSockets vs. AJAX • Fundamentals of WebSockets • Code! • Other Considerations
  5. 5. Ben LimmerGEMConf - 5/21/2016 ember.party request response request response AJAX
  6. 6. Ben LimmerGEMConf - 5/21/2016 ember.party with a lot of apps, this paradigm still works
  7. 7. Ben LimmerGEMConf - 5/21/2016 ember.party but what about real-time apps?
  8. 8. Ben LimmerGEMConf - 5/21/2016 ember.party e.g.
  9. 9. Ben LimmerGEMConf - 5/21/2016 ember.party live dashboards
  10. 10. Ben LimmerGEMConf - 5/21/2016 ember.party Source: http://www.heckyl.com/
  11. 11. Ben LimmerGEMConf - 5/21/2016 ember.party 2nd screen apps
  12. 12. Ben LimmerGEMConf - 5/21/2016 ember.party© MLB / Source: MLB.com
  13. 13. Ben LimmerGEMConf - 5/21/2016 ember.party deployment notifications
  14. 14. Ben LimmerGEMConf - 5/21/2016 ember.party Source: inbox.google.com
  15. 15. Ben LimmerGEMConf - 5/21/2016 ember.party games
  16. 16. Ben LimmerGEMConf - 5/21/2016 ember.party Source: http://browserquest.mozilla.org/img/common/promo-title.jpg
  17. 17. Ben LimmerGEMConf - 5/21/2016 ember.party chat gamesdeployment notifications live dashboards 2nd screen apps activity streams comment sections realtime progress collaborative editing
  18. 18. Ben LimmerGEMConf - 5/21/2016 ember.party how do we build a real-time app?
  19. 19. Ben LimmerGEMConf - 5/21/2016 ember.party update? nope. (old way) short polling update? nope. data update? yep!
  20. 20. Ben LimmerGEMConf - 5/21/2016 ember.party (old way) long polling request Keep-Alive timeout request Keep-Alive data response request Keep-Alive
  21. 21. Ben LimmerGEMConf - 5/21/2016 ember.party WebSockets handshake connection opened bi-directional communication
  22. 22. Ben LimmerGEMConf - 5/21/2016 ember.party WebSockets no polling full duplex over TCP communication over standard HTTP(S) ports broadcast to all connected clients
  23. 23. Ben LimmerGEMConf - 5/21/2016 ember.party Talk Roadmap • WebSockets vs. AJAX • Fundamentals of WebSockets • Code! • Other Considerations
  24. 24. Ben LimmerGEMConf - 5/21/2016 ember.party the handshake
  25. 25. Ben LimmerGEMConf - 5/21/2016 ember.party Request GET wss://example.org/socket HTTP/1.1 Origin: https://example.org Host: example.org Sec-WebSocket-Key: zy6Dy9mSAIM7GJZNf9rI1A== Upgrade: websocket Connection: Upgrade Sec-WebSocket-Version: 13 Response HTTP/1.1 101 Switching Protocols Connection: Upgrade Sec-WebSocket-Accept: EDJa7WCAQQzMCYNJM42Syuo9SqQ= Upgrade: websocket
  26. 26. events • open • message • error • close • send • close methods
  27. 27. Ben LimmerGEMConf - 5/21/2016 ember.party WebSocket.send()
  28. 28. Ben LimmerGEMConf - 5/21/2016 ember.party send(String 'foo'); send(Blob 010101); send(ArrayBuffer file);
  29. 29. Ben LimmerGEMConf - 5/21/2016 ember.party WebSocket.send(’YOLO’);
  30. 30. Ben LimmerGEMConf - 5/21/2016 ember.party sub-protocols • a contract between client/server • 2 classes of sub-protocols • well-defined (e.g. STOMP, WAMP) • application specific protocols
  31. 31. Ben LimmerGEMConf - 5/21/2016 ember.party Request with Protocol GET wss://example.org/socket HTTP/1.1 Origin: https://example.org Host: example.org Sec-WebSocket-Key: zy6Dy9mSAIM7GJZNf9rI1A== Upgrade: websocket Connection: Upgrade Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: v10.stomp
  32. 32. Ben LimmerGEMConf - 5/21/2016 ember.party STOMP SEND destination:/queue/a hello queue a ^@ MESSAGE destination:/queue/a message-id: <message-identifier> hello queue a ^@
  33. 33. Ben LimmerGEMConf - 5/21/2016 ember.party STOMP SEND destination:/queue/a hello queue a ^@
  34. 34. Ben LimmerGEMConf - 5/21/2016 ember.party subprotocols bring structure to ws
  35. 35. Ben LimmerGEMConf - 5/21/2016 ember.party Talk Roadmap • AJAX vs. WebSockets • Fundamentals of WebSockets • Code! • Other Considerations
  36. 36. Ben LimmerGEMConf - 5/21/2016 ember.party let’s build something!
  37. 37. Ben LimmerGEMConf - 5/21/2016 ember.party
  38. 38. Ben LimmerEmberJS Meetup - 2/24/2016 ember.party alice clicks bob / everyone sees
  39. 39. Ben LimmerEmberJS Meetup - 2/24/2016 ember.party bob clicks alice / everyone sees
  40. 40. Ben LimmerGEMConf - 5/21/2016 ember.party npm install ws
  41. 41. Ben LimmerGEMConf - 5/21/2016 ember.party
  42. 42. Ben LimmerGEMConf - 5/21/2016 ember.party • fast • simple WebSocket implementation • few bells and whistles npm install ws
  43. 43. Ben LimmerGEMConf - 5/21/2016 ember.party server/index.js 1 const WebSocketServer = require('ws').Server; 2 3 const wss = new WebSocketServer({ 4 port: process.env.PORT 5 });
  44. 44. Ben LimmerGEMConf - 5/21/2016 ember.party waiting for socket connection…
  45. 45. Ben LimmerGEMConf - 5/21/2016 ember.party ember install ember-websockets
  46. 46. Ben LimmerGEMConf - 5/21/2016 ember.party
  47. 47. Ben LimmerGEMConf - 5/21/2016 ember.party ember install ember-websockets • integrates with the Ember runloop • is an Ember.ObjectProxy • abstracts away the WebSocket
  48. 48. Ben LimmerGEMConf - 5/21/2016 ember.party app/services/rt-ember-socket.js 1 websockets: service(), 2 3 init() { 4 this._super(...arguments); 5 6 const socket = this.get('websockets').socketFor(host); 7 8 socket.on('open', this.open, this); 9 socket.on('close', this.reconnect, this); 10 }, 11 12 online: false, 13 open() { 14 this.set('online', true); 15 }, 16 17 reconnect() { 18 this.set('online', false); 19 20 Ember.run.later(this, () => { 21 this.get('socket').reconnect(); 22 }, 5000); 23 },
  49. 49. Ben LimmerGEMConf - 5/21/2016 ember.party
  50. 50. Ben LimmerGEMConf - 5/21/2016 ember.party rtember-1.0 - sub-protocol
  51. 51. Ben LimmerGEMConf - 5/21/2016 ember.party rtember-1.0 - sub-protocol data events
  52. 52. Ben LimmerGEMConf - 5/21/2016 ember.party server/index.js 1 const WebSocketServer = require('ws').Server; 2 3 const wss = new WebSocketServer({ 4 port: process.env.PORT, 5 handleProtocols: function(protocol, cb) { 6 const supportedProtocol = 7 protocol[protocol.indexOf('rtember-1.0')]; 8 if (supportedProtocol) { 9 cb(true, supportedProtocol); 10 } else { 11 cb(false); 12 } 13 }, 14 });
  53. 53. Ben LimmerGEMConf - 5/21/2016 ember.party
  54. 54. Ben LimmerGEMConf - 5/21/2016 ember.party app/services/rt-ember-socket.js 1 websockets: Ember.inject.service(), 2 3 socket: null, 4 init() { 5 this._super(...arguments); 6 7 const socket = this.get('websockets') 8 .socketFor(host, ['rtember-1.0']); 9 10 socket.on('open', this.open, this); 11 socket.on('close', this.reconnect, this); 12 13 this.set('socket', socket); 14 },
  55. 55. Ben LimmerGEMConf - 5/21/2016 ember.party
  56. 56. Ben LimmerGEMConf - 5/21/2016 ember.party rtember-1.0 - sub-protocol data events
  57. 57. Ben LimmerGEMConf - 5/21/2016 ember.party rtember-1.0 - sub-protocol { "frameType": "event", "payload": { “eventType": ... event type ..., "eventInfo": ... event info ... } } { "frameType": "data", "payload": { ... json api payload ... } } or
  58. 58. Ben LimmerGEMConf - 5/21/2016 ember.party rtember-1.0 - sub-protocol data events
  59. 59. Ben LimmerGEMConf - 5/21/2016 ember.party 1 wss.on('connection', function(ws) { 2 sendInitialGifs(ws); 3 }); 4 5 function sendInitialGifs(ws) { 6 const gifs = gifDb; 7 const random = _.sampleSize(gifs, 25); 8 9 sendDataToClient(ws, serializeGifs(random)); 10 } 11 12 function sendDataToClient(ws, payload) { 13 const payload = { 14 frameType: FRAME_TYPES.DATA, 15 payload, 16 } 17 ws.send(JSON.stringify(payload)); 18 }
  60. 60. Ben LimmerGEMConf - 5/21/2016 ember.party app/services/rt-ember-socket.js 1 init() { 2 ... 3 socket.on('message', this.handleMessage, this); 4 ... 5 }, 6 7 handleMessage(msg) { 8 const { frameType, payload } = JSON.parse(msg.data); 9 10 if (frameType === FRAME_TYPES.DATA) { 11 this.handleData(payload); 12 } else { 13 warn(`Encountered unknown frame type: ${frameType}`); 14 } 15 }, 16 17 handleData(payload) { 18 this.get('store').pushPayload(payload); 19 }
  61. 61. Ben LimmerGEMConf - 5/21/2016 ember.party
  62. 62. Ben LimmerGEMConf - 5/21/2016 ember.party { "frameType": "data", "payload": { "data": [{ "type": "gif", "id": "3o8doPV2heuYjdN2Fy", "attributes": { "url": "http://giphy.com/3o8doPV2heuYjdN2Fy/giphy.gif" } }, { ... }, { ... }] } }
  63. 63. Ben LimmerGEMConf - 5/21/2016 ember.party
  64. 64. Ben LimmerGEMConf - 5/21/2016 ember.party app/routes/index.js 1 export default Ember.Route.extend({ 2 model() { 3 return this.store.peekAll('gif'); 4 } 5 });
  65. 65. Ben LimmerGEMConf - 5/21/2016 ember.party
  66. 66. Ben LimmerGEMConf - 5/21/2016 ember.party app/templates/index.hbs {{gif-tv gifs=model}} app/templates/components/gif-tv.hbs <div class='suggestions'> {{#each gifs as |gif|}} <img src={{gif.url}} /> {{/each}} </div>
  67. 67. Ben LimmerGEMConf - 5/21/2016 ember.party
  68. 68. Ben LimmerGEMConf - 5/21/2016 ember.party
  69. 69. Ben LimmerGEMConf - 5/21/2016 ember.party rtember-1.0 - sub-protocol data events
  70. 70. Ben LimmerGEMConf - 5/21/2016 ember.party rtember-1.0 - sub-protocol { "frameType": "event", "payload": { “eventType": ... event type ..., "eventInfo": ... event info ... } }
  71. 71. Ben LimmerGEMConf - 5/21/2016 ember.party Share GIF Event { "frameType": "event", "payload": { "eventType": "share_gif", "eventInfo": "<gif_id>" } } { "frameType": "data", "payload": {[ <shared_gif>, <previously_shared_gif> ]} }
  72. 72. Ben LimmerGEMConf - 5/21/2016 ember.party { "frameType": "data", "payload": { "data": [ { "type": "gifs", "id": "3o8doPV2heuYjdN2Fy", "attributes": { "url": "http://giphy.com/3o8doPV2heuYjdN2Fy/giphy.gif", "shared": false } }, { "type": "gifs", "id": "xTiQyBOIQe5cgiyUPS", "attributes": { "url": "http://giphy.com/xTiQyBOIQe5cgiyUPS/giphy.gif", "shared": true } } ] } }
  73. 73. Ben LimmerGEMConf - 5/21/2016 ember.party app/templates/components/gif-tv.hbs <div class='suggestions'> {{#each gifs as |gif|}} <img {{action shareGif gif}} src={{gif.url}} /> {{/each}} </div> app/components/gif-tv.js 1 export default Ember.Component.extend({ 2 rtEmberSocket: service(), 3 4 shareGif(gif) { 5 this.get('rtEmberSocket') 6 .sendEvent(EVENTS.SHARE_GIF, gif.get('id')); 7 }, 8 });
  74. 74. Ben LimmerGEMConf - 5/21/2016 ember.party 6 .sendEvent(EVENTS.SHARE_GIF, gif.get('id')); 7 }, 8 }); app/services/rt-ember-socket.js 1 sendEvent(eventType, eventInfo) { 2 this.get('socket').send(JSON.stringify({ 3 frameType: FRAME_TYPES.EVENT, 4 payload: { 5 eventType, 6 eventInfo, 7 }, 8 })); 9 }
  75. 75. Ben LimmerEmberJS Meetup - 2/24/2016 ember.party 1 ws.on('message', function(rawData) { 2 const data = JSON.parse(rawData); 3 4 if (data.frameType === FRAME_TYPES.EVENT) { 5 const newShare = _.find(gifDb, { 6 id: data.payload.eventInfo 7 }); 8 const previouslyShared = _.find(gifDb, 'shared'); 9 10 newShare.shared = true; 11 previouslyShared.shared = false; 12 13 const framePayload = { 14 frameType: FRAME_TYPES.DATA, 15 payload: serializeGifs([previouslyShared, newShare]), 16 }; 17 const rawPayload = JSON.stringify(framePayload); 18 wss.clients.forEach((client) => { 19 client.send(rawPayload); 20 }); 21 } 22 });
  76. 76. Ben LimmerGEMConf - 5/21/2016 ember.party beware
  77. 77. Ben LimmerGEMConf - 5/21/2016 ember.party 1 ws.on('message', function(rawData) { 2 try { 3 const data = JSON.parse(rawData); 4 5 if (data.frameType === FRAME_TYPES.EVENT) { 6 if (data.payload.eventType !== EVENTS.SHARE_GIF) { 7 throw Error(); // unknown event 8 } 9 10 const newShare = ...; 11 if (!newShare) { 12 throw Error(); // unknown gif 13 } 14 ... 15 } 16 } catch(e) { 17 ws.close(1003); // unsupported data 18 } 19 });
  78. 78. Ben LimmerGEMConf - 5/21/2016 ember.party { "frameType": "data", "payload": { "data": [ { "type": "gifs", "id": "3o8doPV2heuYjdN2Fy", "attributes": { "url": "http://giphy.com/3o8doPV2heuYjdN2Fy/giphy.gif", "shared": false } }, { "type": "gifs", "id": "xTiQyBOIQe5cgiyUPS", "attributes": { "url": "http://giphy.com/xTiQyBOIQe5cgiyUPS/giphy.gif", "shared": true } } ] } }
  79. 79. Ben LimmerGEMConf - 5/21/2016 ember.party app/templates/components/gif-tv.hbs app/components/gif-tv.js 1 export default Ember.Component.extend({ 2 sharedGif: computed('gifs.@each.shared', function() { 3 return this.get('gifs').findBy('shared', true); 4 }), 5 }); <div class='shared-gif'> <img src={{sharedGif.url}} /> </div> <div class='suggestions'> <!-- ... --> </div>
  80. 80. Ben LimmerGEMConf - 5/21/2016 ember.party
  81. 81. Ben LimmerGEMConf - 5/21/2016 ember.party ember.party/gemconf
  82. 82. Ben LimmerGEMConf - 5/21/2016 ember.party
  83. 83. Ben LimmerGEMConf - 5/21/2016 ember.party Talk Roadmap • AJAX vs. WebSockets • Fundamentals of WebSockets • Code! • Other Considerations
  84. 84. Ben LimmerGEMConf - 5/21/2016 ember.party other considerations • security • websocket support (libraries) • learn from example
  85. 85. Ben LimmerGEMConf - 5/21/2016 ember.party security • Use TLS (wss:// vs. ws://) • Verify the Origin header • Verify the request by using a random token on handshake source: WebSocket (Andrew Lombardi) - O’Reilly
  86. 86. Ben LimmerGEMConf - 5/21/2016 ember.party other considerations • security • websocket support (libraries) • learn from example
  87. 87. Ben LimmerGEMConf - 5/21/2016 ember.party support
  88. 88. Ben LimmerGEMConf - 5/21/2016 ember.party 9
  89. 89. Ben LimmerGEMConf - 5/21/2016 ember.party socket.io • Graceful fallback to polling / flash (!) • Syntactic Sugar vs. ws package • Support in ember-websockets add-on
  90. 90. Ben LimmerGEMConf - 5/21/2016 ember.party
  91. 91. Ben LimmerGEMConf - 5/21/2016 ember.party pusher.com • Graceful fallback • Presence support • Authentication / Security strategies • No infrastructure required
  92. 92. Ben LimmerGEMConf - 5/21/2016 ember.party other considerations • security • websocket support (libraries) • learn from example
  93. 93. Ben LimmerGEMConf - 5/21/2016 ember.party learn by example
  94. 94. Ben LimmerGEMConf - 5/21/2016 ember.party learn by example
  95. 95. Ben LimmerGEMConf - 5/21/2016 ember.party https://github.com/blimmer/real-time-ember-client https://github.com/blimmer/real-time-ember-server l1m5blimmer
  96. 96. Ben LimmerGEMConf - 5/21/2016 ember.party thanks!
  97. 97. • WebSocket: Lightweight Client-Server Communications (O’Reilly) • WebSockets: Methods for Real-Time Data Streaming (Steve Schwartz) Credits
  98. 98. • pusher.com • socket.io • node ws • websocket security (heroku) Resources

×