Simple bindings to socket.io.
To build everything run npm run build and to run the demo run npm run run and go to localhost:3000 on two tabs. You can send messages back and forth!
Everything lives under the namespace BsSocket. To create a
server/client/namespace, use Server.Make, Client.Make and
Namespace.Make respectively. These functors take a module that
contains 2 things: a type called clientToServer and a type called
serverToClient, which define the type of the message that the client
will send to the server and vice versa.
For example:
module Messages = {
type username = string;
type clientToServer =
| Login(username);
type serverToClient =
| LoginSuccessful(username, bool);
};
module MyServer = BsSocket.Server.Make(Messages);
let io = MyServer.create();A common pattern is to use the same message type for clientToServer and serverToClient in the following way:
module Messages = {
type t = ...;
type clientToServer = t;
type serverToClient = t;
};See example/ folder for full usage.
The API differs a bit from socket.io's API to be more idiomatic in
Reason. Generally, e.g. JavaScript's socket.emit("bla", 10) becomes
Server.emit(socket, Bla(10)) in Reason.
Whereas in socket.io, emitting a message requires a string as the
first argument as a way to tag what type of message you're sending, in
bs-socket.io, the intended usage is that there is only one type of
message and therefore there is no string tag required. This is so
that the function that handles messages from the other side of the
socket, i.e. MyClient.on and MyServer.on, can leverage Reason's
exhaustive pattern matching on variant types, which can help ensure
that they are handling all potential message variants that they could
possibly be sent.
As a concrete example, the following socket.io and bs-socket.io pseudo-code would be analogous to each other.
Socket.io:
// client
socket.emit('login', { username: 'user2157' });
socket.emit('chat message', 'hello');
// server
socket.on('login', msg => ...);
socket.on('chat message', msg => ...);Bs-socket.io:
// client
MyClient.emit(client, Login("user2157"));
MyClient.emit(client, ChatMessage("hello"));
// server
MyServer.Socket.on(socket, msg =>
switch(msg) {
| Login(username) => ...
| ChatMessage(msg) => ...
);If you later extended the type of clientToServer to have another case, i.e.
type username = string;
type clientToServer =
| Login(username)
| ChatMessage(string)
| Logout(username);Then the previous implementation of MyServer.on would no longer
compile with the reason that you haven't handled the Logout variant
of the clientToServer type.
There are a couple differences between the JS API and this one. We'll refer to the supposed module you've created from the Server.Make functor as MyServer. Same for Namespace and Client.
MyServer.emitis different fromMyServer.Socket.emit. The former emits to all connected peers while the latter emits to the given peer.- Instead of
io.socketsthere isMyNamespace.default(io)which does the same thing. - Instead of
io.ofthere isMyNamespace.of_(io)which does the same thing. (ofis a Reason keyword) - All functions that are overloaded have different names depending on what you're passing. There's
MyServer.createbut alsoMyServer.createWithHttp(see example) among others.