The ws-client.js module will handle communicating with your Node WebSocket server.
It will have four responsibilities:
connecting to the server
performing initial setup when the connection is first opened
forwarding incoming messages to their handlers
sending outgoing messages
Check out how those responsibilities relate to your other components (Figure 17.14).
As you build out your client, you will get a tour of some new ES6 features as well.
First, build out your collection handling. Begin by opening ws-client.js and declaring a variable for the WebSocket connection.
let socket;
This declaration uses a new way of defining variables in ES6
called let scoping. If you use let scoping
to declare a variable – using the keyword
let
instead of var
–
your variable will not be hoisted.
Hoisting means that the variable declarations get moved to the top of the function scope in which they are created. This is something that the JavaScript interpreter does behind the scenes. Unfortunately, it can lead to hard-to-find errors.
You will read more about hoisting at the end of the chapter. For now, know that
let
is a safer way to declare variables
in if/else
clauses
and in the body of loops.
Now, add a method to ws-client.js to initialize your connection.
let socket; function init(url) { socket = new WebSocket(url); console.log('connecting...'); }
The init function connects to the WebSockets server. Next, you want to wire up ws-client.js to ChatApp in app.js.
To be a functioning module, ws-client.js needs to specify what
it exports. You need to export a single
value: an object code with the exported functions as its
properties. You are going to use the same export default
syntax that you
used at the beginning of the chapter – plus an additional bit of
ES6 handiness.
Add the export to the end of ws-client.js, as shown.
... function init(url) { socket = new WebSocket(url); console.log('connecting...'); } export default { init, }
Notice that you did not have to specify the property names. This syntactic shortcut is the equivalent of:
export default { init: init }
If the key and value have the same name, ES6 allows you to omit the colon and the value. The key will automatically be the variable name, and the value will automatically be the value associated with that name. This feature of ES6 is the enhanced object literal syntax.
Now that you have the ws-client module set up, it is time to import the values it provides in app.js. Begin by adding an import statement to the top of app.js:
import socket from './ws-client'; class ChatApp { constructor() { console.log('Hello ES6!'); } } ...
socket will be the object you exported from ws-client.js.
Next, in the ChatApp constructor, call socket.init with the URL of your WebSocket server.
import socket from './ws-client'; class ChatApp { constructor() {console.log('Hello ES6!');socket.init('ws://localhost:3001'); } } ...
Your npm
script should rebuild the code for you.
(You may need to restart npm run watch
and npm run dev
in separate windows, if you have let one or both of them stop.)
Reload your
browser and you should see
'connecting...'
logged to the
console, as shown in Figure 17.15.
With that, you have the skeleton of your app up and running.
When your App
module calls init,
a new WebSocket object is instantiated
and a connection is made to the server.
But your App
module needs to know when
this process has completed so that it can do something
with the connection.
The WebSocket object has a set of special properties for handling events. One of these is the onopen property. Any function assigned to this property will be called when the connection to the WebSocket server is made. Inside this function, you can carry out any steps that need to be made upon connecting.
In order for the ws-client
module to be
flexible and reusable, you will not hardcode the steps
that the App
module needs to make upon connecting.
Instead, you will use the same pattern you used for registering
click and submit handlers in CoffeeRun.
Add a function called registerOpenHandler to ws-client.js. registerOpenHandler will accept a callback, assign a function to onopen, and then invoke the callback inside the onopen function.
let socket; function init(url) { socket = new WebSocket(url); console.log('connecting...'); } function registerOpenHandler(handlerFunction) { socket.onopen = () => { console.log('open'); handlerFunction(); }; } ...
This function definition is different from what you have written before. This is a new ES6 syntax called an arrow function. Arrow functions are a shorthand for writing anonymous functions. Apart from being a bit easier to write, arrow functions work exactly the same as anonymous functions.
registerOpenHandler takes a function argument
(handlerFunction) and assigns an anonymous
function to the onopen
property of the socket connection.
Inside of this anonymous
function, you call the handlerFunction that was passed in.
(Using an anonymous function is more complicated than writing
socket.onopen = handlerFunction
.
This pattern will serve you well when you
need to respond to an event but
have intermediary steps that must happen
before forwarding it on –
like writing a log message, as you have done here.)
Next, you need to write an interface for handling messages as they come in over your WebSockets connection. Write a new method called registerMessageHandler in ws-client.js. Assign an arrow function to the socket’s onmessage property; this arrow function should expect to receive an event argument.
... function registerOpenHandler(handlerFunction) { socket.onopen = () => { console.log('open'); handlerFunction(); }; } function registerMessageHandler(handlerFunction) { socket.onmessage = (e) => { console.log('message', e.data); let data = JSON.parse(e.data); handlerFunction(data); }; } ...
Arrow function parameters go inside the parentheses, just as they do for regular functions.
The Chattrbox client receives an object from the server in its onmessage callback inside registerMessageHandler. This object represents the event and has a data property that contains the JSON string from the server. Each time you receive a string, you convert the string to a JavaScript object. You then forward it along to handlerFunction.
The last bit is the piece that will actually send the message to your WebSocket. Write this in ws-client.js as a function called sendMessage. You will do this in two parts. First, you will turn your message payload (containing the message, the username, and the timestamp) into a JSON string. Then you will send that JSON string to the WebSocket server.
... function registerMessageHandler(handlerFunction) { socket.onmessage = (e) => { console.log('message', e.data); let data = JSON.parse(e.data); handlerFunction(data); }; } function sendMessage(payload) { socket.send(JSON.stringify(payload)); } ...
Finally, add exports for your new methods using the enhanced object literal syntax.
... function sendMessage(payload) { socket.send(JSON.stringify(payload)); } export default { init, registerOpenHandler, registerMessageHandler, sendMessage }
With that, ws-client.js has everything it needs to communicate back and forth with the server. Your last job in ws-client.js will be to test it by sending a message.
Update the ChatApp constructor in app.js. After calling socket.init, call registerOpenHandler and registerMessageHandler, passing them arrow functions.
import socket from './ws-client'; class ChatApp { constructor() { socket.init('ws://localhost:3001'); socket.registerOpenHandler(() => { let message = new ChatMessage({ message: 'pow!' }); socket.sendMessage(message.serialize()); }); socket.registerMessageHandler((data) => { console.log(data); }); } } ...
When the connection is open, you are immediately sending a dummy message. And when a message is received, you are logging it to the console.
Save your code and reload the browser when the build process finishes. You should see that a message was sent and echoed back (Figure 17.16).
Excellent work! You have two of the three primary modules for Chattrbox working. You will finish Chattrbox in the next chapter by creating a module that connects your existing modules to the UI. This module will draw new messages to the message list and send messages when the form is submitted.
3.15.220.16