Channels

The channels create independent communication spaces on a WebSocket connection.

Each channel defines its own life cycle and its own events. Several sockets can join the same instance and communicate without mixing their events with those of the main socket.

Servidor:

gox
channel.On("message", func(socket *gox.Socket, message string) {
    channel.Emit("message", socket.id(), message)
})

Cliente:

js
const chat = new socket.Channel("Chat#general")

chat.on("message", (socketID, message) => {
    console.log(socketID, message)
})

chat.emit("message", "Hola, George")

Declaration

Each channel is declared within the directory websocket.

text
my-project/
└── websocket/
    └── chat/
        └── chat.gox

The file must use a module and an input function with the same channel name:

gox
module chat

import "github.com/gox-lang/gox"

func Chat(channel *gox.Channel) gox.Channel {
    channel.On("message", func(socket *gox.Socket, message string) {
        channel.Emit("message", socket.id(), message)
    })
}

The function is given an instance of gox.Channel and records his behavior.

GOX loads the definition when a client tries to join the channel for the first time. It is not necessary to create or register the instance manually.


Client-side creation

The channels are created by an existing WebSocket connection:

js
import { Socket } from "@gox/websocket"

const socket = new Socket()
const chat = new socket.Channel("Chat")

Create the channel immediately starts the binding process.

The name used in JavaScript should match the server definition. In this case, Chat corresponds to the function Chat declared in websocket/chat/chat.gox.

Each object maintains its own life cycle and its own handlers:

js
chat.when("connected", isReconnect => {
    console.log("Canal conectado")
})

chat.when("rejected", () => {
    console.log("No fue posible unirse al canal")
})

Dynamic instances

The same definition can create several independent bodies using #:

js
const general = new socket.Channel("Chat#general")
const support = new socket.Channel("Chat#support")

Each channel has a name, key and identifier:

PropertyChat#generalDescription
nameChatIdentify the channel definition.
keygeneralIt identifies the dynamic instance.
idChat#generalIdentify the full instance.

These properties are also available in Go:

gox
name := channel.Name()
key := channel.Key()
id := channel.ID()

The sockets attached to Chat#general do not belong to Chat#support. Each instance maintains its own members and events.

A channel without # uses an empty key:

js
const chat = new socket.Channel("Chat")

console.log(chat.name) // "Chat"
console.log(chat.key)  // ""
console.log(chat.id)   // "Chat"

Validation

The event validate decide if a socket can join the channel.

gox
channel.When("validate", func(socket *gox.Socket, token string) bool {
    return token != ""
})

The validation values are sent after the identifier:

js
const chat = new socket.Channel("Chat#general", token)

The first argument received in Go is always the socket that tries to join. The following arguments correspond, in the same order, to the values sent from JavaScript.

The handler must return a boolean value:

  • true He accepts the union.
  • false He rejects the union.

If not registered validate, GOX accepts the connection automatically.

Use this event to check permissions or information needed to access the channel.


Lifecycle

Life cycle events are recorded by When() in Go and when() in JavaScript.

Servidor:

gox
channel.When("connected", func(socket *gox.Socket) {
    channel.Emit("memberConnected", socket.id())
})

channel.When("disconnected", func(socket *gox.Socket, reason gox.ChannelDisconnectReason) {
    channel.Emit("memberDisconnected", socket.id())
})

Cliente:

js
chat.when("connected", isReconnect => {
    console.log("Conectado al canal", isReconnect)
})

chat.when("disconnected", reason => {
    console.log("Desconectado del canal:", reason)
})

chat.when("error", error => {
    console.error(error)
})

connected it runs after accepting the union. In the client receives false during the first connection and true when the channel reunites after a socket reconnection.

disconnected It runs when the socket leaves the channel, is removed or loses its connection.

error reports errors produced during validation, life cycle or event exchange.

The concepts of when follow the same model explained in Lifecycle. This page shows only the specific events of the channels.


Rejection

The event rejected the client runs when validate return false:

js
chat.when("rejected", () => {
    console.log("No fue posible unirse al canal")
})

rejected He's not getting any arguments.

After the rejection, the channel is in state rejected And he doesn't try to join again when the socket reconnects.

A rejection produced by false is a normal result and just runs rejected. If validation fails by an internal error, the client receives first error and then rejected.


Events

The events sent from JavaScript are recorded with On() in Go:

gox
channel.On("message", func(socket *gox.Socket, message string) {
    channel.Emit("message", socket.id(), message)
})

The first argument of the handler is the socket that originated the event. The values sent by the client are received below:

js
chat.emit("message", "Hola, George")

channel.Emit() sends the event to all sockets belonging to the same instance:

gox
channel.Emit("message", socket.id(), message)

Each client receives it by means of on():

js
chat.on("message", (socketID, message) => {
    console.log(socketID, message)
})

The events of a channel remain separate from the events recorded directly on the socket.

See Events to know the rules of on, emit and the compatible values.


Requests

The client may request a response to a channel event:

js
const profile = await chat.emit("profile", userID)

Go's handler must return gox.EventReply:

gox
channel.On("profile", func(socket *gox.Socket, userID string) gox.EventReply {
    return gox.EventReply{
        "id": userID,
        "name": "George",
    }
})

Channel requests work only from the client to the server. channel.Emit() in Go transmits events to the members and does not expect responses.

See Requests to know the operation of the requests and gox.EventReply.


Leave the channel

leave() removes the client from the current instance:

js
chat.leave()

The server receives the event disconnected:

gox
channel.When("disconnected", func(socket *gox.Socket, reason gox.ChannelDisconnectReason) {
    log.Printf("%s salió del canal: %s", socket.id(), reason)
})

The client also receives disconnected for the corresponding reason:

js
chat.when("disconnected", reason => {
    console.log(reason)
})

When you get out by leave()The reason is, left.

GOX also automatically eliminates the socket from its channels when the connection ends.


Full example

The following example creates dynamic instances of a chat, validates access and distributes messages among its members.

Servidor:

gox
module chat

import "github.com/gox-lang/gox"

func Chat(channel *gox.Channel) gox.Channel {
    channel.When("validate", func(socket *gox.Socket, token string) bool {
        return token != ""
    })

    channel.When("connected", func(socket *gox.Socket) {
        channel.Emit("memberConnected", socket.id())
    })

    channel.When("disconnected", func(socket *gox.Socket, reason gox.ChannelDisconnectReason) {
        channel.Emit("memberDisconnected", socket.id())
    })

    channel.On("message", func(socket *gox.Socket, message string) {
        channel.Emit("message", socket.id(), message)
    })
}

Cliente:

js
import { Socket } from "@gox/websocket"

const socket = new Socket()
const chat = new socket.Channel("Chat#general", token)

chat.when("connected", isReconnect => {
    console.log("Conectado al canal", isReconnect)
})

chat.when("rejected", () => {
    console.log("Acceso rechazado")
})

chat.when("disconnected", reason => {
    console.log("Desconectado:", reason)
})

chat.when("error", error => {
    console.error(error)
})

chat.on("memberConnected", socketID => {
    console.log("Conectado:", socketID)
})

chat.on("memberDisconnected", socketID => {
    console.log("Desconectado:", socketID)
})

chat.on("message", (socketID, message) => {
    console.log(socketID, message)
})

chat.when("connected", () => {
    chat.emit("message", "Hola, George")
})

All client accepted in Chat#general they share the same authority and receive their emissions.


Global administration

This page explains how to declare a channel and use it from a connection.

To consult active channels, access its members or close an instance from other server areas, use the global record gox.Channels.

See Server channels to know the global administration of channels.


Best practices

  • It uses channels to isolate communication areas with its own behavior.
  • It uses keys when a definition requires several independent instances.
  • Valid the permits before you accept a socket.
  • Drive rejected, disconnected and error on the client.
  • Search the handlers before depending on the channel connection.
  • Do not mix the channel events with the general socket events.
  • It uses requests only when the client needs a response.
  • Executed leave() when the client no longer needs to participate in the channel.

To learn more about SocketSee your documentation. To learn more about Communication between JavaScript and Go, check your documentation.


See also

Next step

Continue with Communication between JavaScript and Go to know the values that can be exchanged between the two environments.

Communication between JavaScript and Go