With neffos you can send any type of data, remember? The `neffos.Message.Body` field is just a `[]byte`. At short, the `neffos.Message.Body` is the raw data client/server sends, users of the neffos package can use any format to unmarshal on read and marshal to send, such as `protocol-buffers`, `encoding/json`, `encoding/xml` and etc. In this section you will learn how to send `JSON` data per [[Room]] using the `Emit` with `neffos.Marshal` and how to read those data inside an event callback's with the `neffos.Message.Unmarshal` method. The `neffos.DefaultMarshaler/Unnmarshaler` will be used on `neffos.Marshal` and `neffos.Message.Unmarshal` if the value you are trying to send/read does not complete the `neffos.MessageObjectMarshaler` on send and `neffos.MessageObjectUnmarshaler` on read, therefore the default encoding that neffos using for struct values is the JSON one. Below you will find a brief outline of the above: ```go package neffos var ( DefaultMarshaler = json.Marshal DefaultUnmarshaler = json.Unmarshal ) type ( MessageObjectMarshaler interface { Marshal() ([]byte, error) } MessageObjectUnmarshaler interface { Unmarshal(body []byte) error } ) type Message struct { // [...] } func (m *Message) Unmarshal(outPtr interface{}) error { if unmarshaler, ok := outPtr.(MessageObjectUnmarshaler); ok { return unmarshaler.Unmarshal(m.Body) } return DefaultUnmarshaler(m.Body, outPtr) } ``` ## Create our userMessage structure ```go // file: main.go // userMessage implements the `neffos.MessageBodyMarshaler` `neffos.MessageBodyUnmarshaler`. type userMessage struct { From string `json:"from"` Text string `json:"text"` } // Defaults to `DefaultUnmarshaler & DefaultMarshaler` that are calling the json.Unmarshal & json.Marshal respectfully // if the instance's Marshal and Unmarshal methods are missing, // however for the example shake we complete the MessageBodyMarshaler and MessageBodyUnmarshaler too, // these can help you customize the out and in format. func (u *userMessage) Marshal() ([]byte, error) { return json.Marshal(u) } func (u *userMessage) Unmarshal(b []byte) error { return json.Unmarshal(b, u) } ``` ## Define the server and client events ```go // file: main.go var serverAndClientEvents = neffos.Namespaces{ namespace: neffos.Events{ neffos.OnNamespaceConnected: func(c *neffos.NSConn, msg neffos.Message) error { log.Printf("[%s] connected to namespace [%s].", c, msg.Namespace) return nil }, neffos.OnNamespaceDisconnect: func(c *neffos.NSConn, msg neffos.Message) error { log.Printf("[%s] disconnected from namespace [%s].", c, msg.Namespace) return nil }, neffos.OnRoomJoined: func(c *neffos.NSConn, msg neffos.Message) error { text := fmt.Sprintf("[%s] joined to room [%s].", c, msg.Room) log.Printf("\n%s", text) // notify others. if !c.Conn.IsClient() { c.Conn.Server().Broadcast(c, neffos.Message{ Namespace: msg.Namespace, Room: msg.Room, Event: "notify", Body: []byte(text), }) } return nil }, neffos.OnRoomLeft: func(c *neffos.NSConn, msg neffos.Message) error { text := fmt.Sprintf("[%s] left from room [%s].", c, msg.Room) log.Printf("\n%s", text) // notify others. if !c.Conn.IsClient() { c.Conn.Server().Broadcast(c, neffos.Message{ Namespace: msg.Namespace, Room: msg.Room, Event: "notify", Body: []byte(text), }) } return nil }, "chat": func(c *neffos.NSConn, msg neffos.Message) error { if !c.Conn.IsClient() { c.Conn.Server().Broadcast(c, msg) } else { var userMsg userMessage err := msg.Unmarshal(&userMsg) if err != nil { log.Fatal(err) } fmt.Printf("%s >> [%s] says: %s\n", msg.Room, userMsg.From, userMsg.Text) } return nil }, // client-side only event to catch any server messages comes from the custom "notify" event. "notify": func(c *neffos.NSConn, msg neffos.Message) error { if !c.Conn.IsClient() { return nil } fmt.Println(string(msg.Body)) return nil }, }, } ``` ## Create and run our Server ```go // file: main.go func startServer() { server := neffos.New(gobwas.DefaultUpgrader, serverAndClientEvents) server.IDGenerator = func(w http.ResponseWriter, r *http.Request) string { if userID := r.Header.Get("X-Username"); userID != "" { return userID } return neffos.DefaultIDGenerator(w, r) } server.OnUpgradeError = func(err error) { log.Printf("ERROR: %v", err) } server.OnConnect = func(c *neffos.Conn) error { if c.WasReconnected() { log.Printf("[%s] connection is a result of a client-side re-connection, with tries: %d", c.ID(), c.ReconnectTries) } log.Printf("[%s] connected to the server.", c) // if returns non-nil error then it refuses the client to connect to the server. return nil } server.OnDisconnect = func(c *neffos.Conn) { log.Printf("[%s] disconnected from the server.", c) } log.Printf("Listening on: %s\nPress CTRL/CMD+C to interrupt.", addr) http.Handle("/", http.FileServer(http.Dir("./browser"))) http.Handle(endpoint, server) log.Fatal(http.ListenAndServe(addr, nil)) } ``` ## Create our (Go) Client side ```go // file: main.go func startClient() { scanner := bufio.NewScanner(os.Stdin) fmt.Print("Please specify a username: ") if !scanner.Scan() { return } username := scanner.Text() // init the websocket connection by dialing the server. client, err := neffos.Dial( // Optional context cancelation and deadline for dialing. nil, // The underline dialer, can be also a gobwas.Dialer/DefautlDialer or a gorilla.Dialer/DefaultDialer. // Here we wrap a custom gobwas dialer in order to send the username among, on the handshake state, // see `startServer().server.IDGenerator`. gobwas.Dialer(gobwas.Options{Header: gobwas.Header{"X-Username": []string{username}}}), // The endpoint, i.e ws://localhost:8080/path. addr+endpoint, // The namespaces and events, can be optionally shared with the server's. serverAndClientEvents) if err != nil { log.Fatal(err) } defer client.Close() go func() { <-client.NotifyClose os.Exit(0) }() // connect to the "default" namespace. c, err := client.Connect(nil, namespace) if err != nil { log.Fatal(err) } askRoom: fmt.Print("Please specify a room to join, i.e room1: ") if !scanner.Scan() { log.Fatal(scanner.Err()) } roomToJoin := scanner.Text() room, err := c.JoinRoom(nil, roomToJoin) if err != nil { log.Fatal(err) } fmt.Fprint(os.Stdout, ">> ") for { if !scanner.Scan() { log.Printf("ERROR: %v", scanner.Err()) break } text := scanner.Text() if text == "exit" { break } if text == "leave" { room.Leave(nil) goto askRoom } // username is the connection's ID == // room.String() returns -> NSConn.String() returns -> Conn.String() returns -> Conn.ID() // which generated by server-side via `Server#IDGenerator`. userMsg := userMessage{From: username, Text: text} room.Emit("chat", neffos.Marshal(userMsg)) fmt.Fprint(os.Stdout, ">> ") } } ``` ## Create our (Javascript browserify) Client side In this example, we make an exception and we include the browser-side as well, so you can have a small taste of it. Read more about [neffos.js](https://github.com/kataras/neffos.js). ```js // file: browser/app.js const neffos = require('neffos.js'); var scheme = document.location.protocol == "https:" ? "wss" : "ws"; var port = document.location.port ? ":" + document.location.port : ""; var wsURL = scheme + "://" + document.location.hostname + port + "/echo"; var outputTxt = document.getElementById("output"); function addMessage(msg) { outputTxt.innerHTML += msg + "\n"; } function handleError(reason) { console.log(reason); window.alert(reason); } class UserMessage { constructor(from, text) { this.from = from; this.text = text; } } async function handleNamespaceConnectedConn(nsConn) { const roomToJoin = prompt("Please specify a room to join, i.e room1: "); nsConn.joinRoom(roomToJoin); let inputTxt = document.getElementById("input"); let sendBtn = document.getElementById("sendBtn"); sendBtn.disabled = false; sendBtn.onclick = function () { const input = inputTxt.value; inputTxt.value = ""; switch (input) { case "leave": nsConn.room(roomToJoin).leave(); // or room.leave(); break; default: const userMsg = new UserMessage(nsConn.conn.ID, input); nsConn.emit("chat", neffos.marshal(userMsg)); addMessage("Me: " + input); } }; } async function runExample() { // You can omit the "default" and simply define only Events, the namespace will be an empty string"", // however if you decide to make any changes on this example make sure the changes are reflecting inside the ../server.go file as well. try { const username = prompt("Please specify a username: "); const conn = await neffos.dial(wsURL, { default: { // "default" namespace. _OnNamespaceConnected: function (nsConn, msg) { addMessage("connected to namespace: " + msg.Namespace); handleNamespaceConnectedConn(nsConn); }, _OnNamespaceDisconnect: function (nsConn, msg) { addMessage("disconnected from namespace: " + msg.Namespace); }, _OnRoomJoined: function (nsConn, msg) { addMessage("joined to room: " + msg.Room); }, _OnRoomLeft: function (nsConn, msg) { addMessage("left from room: " + msg.Room); }, notify: function (nsConn, msg) { addMessage(msg.Body); }, chat: function (nsConn, msg) { // "chat" event. const userMsg = msg.unmarshal() addMessage(userMsg.from + ": " + userMsg.text); } } }, { headers: { 'X-Username': username }, // if > 0 then on network failures it tries to reconnect every 5 seconds, defaults to 0 (disabled). reconnect: 5000 }); conn.connect("default"); } catch (err) { handleError(err); } } runExample(); ``` ## Third-party Requirements - [NPM](https://nodejs.org) ## How to run Open a terminal window instance and execute: ```sh $ cd ./browser # build the browser-side client: ./browser/bundle.js which ./browser/index.html imports. $ npm install && npm run-script build $ cd ../ $ go run main.go server # start the neffos websocket server. ``` Open some web browser windows and navigate to , each window will ask for a username and a room to join, each window(client connection) and server get notified for namespace connected/disconnected, room joined/left and chat events. To start the go client side just open a new terminal window and execute: ```sh $ go run main.go client ``` It will ask you for username and a room to join as well, it acts exactly the same as the `./browser/app.js` browser-side application. Read the full source code of this example by navigating to the repository's [_examples/basic](https://github.com/kataras/neffos/tree/master/_examples/basic) directory. You can continue by learning how to send and receive [[Protobufs]].