Skip to main content

Broadcasting

Formidable Events Broadcaster is a user-friendly solution for implementing real-time event broadcasting between your server and frontend applications. By leveraging server-sent events (SSE), broadcasting enables seamless communication and instant updates whenever important events occur.

With Formidable Events Broadcaster, you can easily push notifications, update live data, and trigger actions on the frontend in response to server-side events. The beauty of broadcasting lies in its simplicity and efficiency, eliminating the need for complex setups like AJAX polling or WebSocket configurations.

Let's consider a simple example. Imagine you're building a live chat application. With broadcasting, new chat messages can be instantly sent from the server to all connected clients. As soon as a user sends a message, the server broadcasts it to all other participants, updating their chat interface in real-time.

Getting Started

Formidable Events Broadcaster doesn't come pre-installed with Formidable, so you'll need to install and configure it yourself. Not to worry, though, it's a breeze to set up.

Prerequisites

Installation

Install the package using your preferred package manager:

npm install @formidablejs/broadcaster

Publish

Once installed, we can publish the vendor files:

node craftsman package:publish --package=@formidablejs/broadcaster --tag=vendor

This will publish the following files:

├── app
│ └── Resolvers
│ └── BroadcastServiceResolver.{ts,imba}
├── config
│ └── broadcaster.{ts,imba}
└── routes
└── channels.{ts,imba}

Next, you will need to register the broadcasting config file in the config/index.imba or config/index.ts file:

config/index.ts
...
import broadcasting from './broadcasting'
...
export class Config extends ConfigRepository
{
/**
* All of the configuration items.
*/
get registered(): object
{
return {
...
broadcasting,
...
}
}
}

And finally, register the BroadcastServiceResolver in the bootstrap/resolvers.imba or bootstrap/resolvers.ts file:

bootstrap/resolvers.ts
...
import BroadcastServiceResolver from '../app/Resolvers/BroadcastServiceResolver'
...

export default [
...
BroadcastServiceResolver,
...
]

Configuration

The broadcasting configuration file is located at config/broadcasting.{ts,imba}. This file allows you to configure the redis connection, the channels prefix and middleware. In most cases, you will not need to modify this file. Broadcasting will work out of the box with the default configuration. However, you may need to modify the configuration if you want to use a different redis connection, prefix or middleware.

Prefix

The prefix option allows you to configure the prefix for all channels path:

config/broadcasting.ts
export default {
...
prefix: '_broadcast',
...
}
info

Changing the prefix option in your config, will require you to also change it in the bootstrap file located at resources/js/bootstrap.ts or resources/frontend/bootstrap.imba:

resources/js/bootstrap.ts
window.BroadcastConfig = {
prefix: '_broadcast',
}

Middleware

The middleware option allows you to configure the middleware that will be applied to all channels:

config/broadcasting.ts
export default {
...
middleware: ['csrf:allow-get'],
...
}

Redis

The redis object allows you to configure the redis connection name and expiration information:

config/broadcasting.ts
export default {
...
redis: {
connection: env('BROADCAST_CONNECTION', 'cache'),
publish_mode: env('BROADCAST_PUBLISH_MODE', 'overwrite'),
refresh_rate: env('BROADCAST_REFRESH_RATE', 100),
expiration: {
mode: env('BROADCAST_EXPIRATION_MODE', 'PX'),
ttl: env('BROADCAST_EXPIRATION_TTL', 1000)
}
},
...
}

Cache Configuration

Finally, you may cache all of your broadcasting configuration into a single file using the config:cache Artisan command. This will combine all of your broadcasting configuration options into a single file which will be loaded quickly by the framework. Caching your configuration provides a significant performance boost when configuring the broadcasting service for the first time:

node craftsman config:cache
tip

Whenever you make changes to the broadcasting configuration, you should run the config:cache command. This will clear the configuration cache so that fresh configuration values will be loaded on the next request.

Defining Channels

Channels are broadcasting events that clients can listen to. For example, a chat application may broadcast messages to a conversation channel. All clients listening on that channel will receive the message. Channels may be defined using the channel method on the Broadcast class. The channel method accepts 2 arguments: the channel name and an optional callback that can be used to authorize the channel:

routes/channels.ts
import { Broadcast } from '@formidablejs/broadcaster'

Broadcast.channel('chat')

Naming Channels

You may assign a name to a channel using the name method. This name will be used to identify the channel when broadcasting messages. The name method accepts a single argument: the channel name:

routes/channels.ts
import { Broadcast } from '@formidablejs/broadcaster'

Broadcast.channel('chat').name('chat')

Authorizing Channels

Before broadcasting to a channel, you should authorize that the currently authenticated user can actually listen on the channel. For example, if you are broadcasting to a private chat channel, you should verify that the authenticated user is actually authorized to listen on that channel. You may do this by checking if a User property is valid on the data payload:

routes/channels.ts
import { Broadcast } from '@formidablejs/broadcaster'

Broadcast.channel('chat', message => message.user !== null).name('chat')

If the channel method returns false, the user will be denied access to the channel. If the channel method returns true, the user will be authorized to listen on the channel.

Parameterized Channels

Sometimes you may need to broadcast to a channel that requires parameters. For example, you may need to broadcast to a specific user's chat channel. You may accomplish this by passing your channel parameters as channel parameters to the channel method:

routes/channels.ts
import { Broadcast } from '@formidablejs/broadcaster'
import { ConversationRepository } from '../app/Repositories/ConversationRepository'

Broadcast.channel('chat/:chat_id/:conversation_id', ({ user, params }) => {
return ConversationRepository.canAccess(user, params.chat_id, params.conversation_id)
})

Class-Based Channels

You may also define channels using a class-based approach. This approach gives you more control over the channel's behavior and allows you to define additional methods and properties on the channel class.

To get started, you can use the make:channel Craftsman command to generate a new channel class:

node craftsman make:channel ConversationChannel

This command will create a new channel class in the app/Broadcasting directory. You may then define the channel's behavior within the class:

app/Broadcasting/ConversationChannel.ts
import { BroadcastChannel } from '@formidablejs/broadcaster'
import type { ChannelMessage, ConnectionEvent } from '@formidablejs/broadcaster'

export class ConversationChannel extends BroadcastChannel
{
/**
* Subscribes a user to the channel.
*/
subscribe(event: ConnectionEvent): void
{
console.log(`Subscribed to "${this.name}" 🎉`)
}

/**
* Unsubscribes a user from the channel.
*/
unsubscribe(event: ConnectionEvent): void
{
console.log(`Unsubscribed from "${this.name}" 👋`)
}

/**
* Publishes a message to the channel.
*/
publish(message: ChannelMessage): boolean
{
return true
}
}

Once you have defined your channel class, you can register it in the routes/channels.{ts,imba} file:

routes/channels.ts
import { Broadcast } from '@formidablejs/broadcaster'
import { ConversationChannel } from '../app/Broadcasting/ConversationChannel'

Broadcast.channel('chat', ConversationChannel)

Understanding ConnectionEvent Object

The ConnectionEvent object contains information about the connection event. It has the following properties:

PropertyTypeDescription
userUser?The authenticated user.
userAgentstring?The user agent of the client.
paramsobject?The channel parameters.
queryobject?The query parameters.
event`openclose
errorError?The error object if an error occurred.

Understanding ChannelMessage Object

The ChannelMessage object contains information about the message being broadcast. It has the following properties:

PropertyTypeDescription
idstringThe message ID.
userUser?The user who is waiting for the message.
userAgentstring?The user agent of the client.
paramsobject?The channel parameters.
queryobject?The query parameters.
payloadobjectThe message payload.
connectionnumberThe connection number.

Broadcasting To Channels

To Broadcast to a channel, you can use the Channel class:

import { Channel } from '@formidablejs/broadcaster'

Channel.publish('message').on('channel-name')

Listening For Broadcasts

To listen for broadcasts, you may use the subscribe helper function from the @formidablejs/broadcaster package. The subscribe function accepts two arguments: the channel name and a options object. The options object may contain the following properties:

PropertyTypeDescription
onMessageFunctionThe callback that will be called when a message is received.
onErrorFunctionThe callback that will be called when an error occurs.
onReadyFunctionThe callback that will be called when the connection is ready.

The subscribe function returns a EventSource instance. You may use this instance to close the connection or to check the connection state.

Subscribing To A Channel

As mentioned above, the subscribe function accepts two arguments: the channel name and a options object. To subscribe to a channel, you may use the following syntax:

resources/js/Pages/Chat.vue
<script lang="ts" setup>
import { subscribe } from '@formidablejs/broadcaster/client'
import { onMounted, ref } from 'vue'

const messages = ref<string[]>([]);

onMounted(() => {
subscribe('chat', {
onMessage: (message: string) => messages.value.push(message),
})
})
</script>

<template>
<div>
<div v-for="(message, i) in messages" :key="i">{{ message }}</div>
</div>
</template>

The subscribe function will return a EventSource instance. You may use this instance to close the connection or to check the connection state.

That's it! You're now listening for broadcasts on the chat channel. Any messages broadcast to the chat channel will be received by the onMessage callback.

Subscribing To A Parameterized Channel

Subscribing to a parameterized channel is similar to subscribing to a regular channel. The only difference is that you need to pass the channel parameters in the channel name. For example, if you have a channel named chat/:chat_id/:conversation_id, you may subscribe to it using the following syntax:

resources/js/Pages/Chat.vue
<script lang="ts" setup>
import { subscribe } from '@formidablejs/broadcaster/client'
import { onMounted, ref } from 'vue'

const messages = ref<string[]>([]);
const chatId = 1;
const conversationId = 1;

onMounted(() => {
subscribe(`chat/${chatId}/${conversationId}`, {
onMessage: (message: string) => messages.value.push(message),
})
})
</script>

<template>
<div>
<div v-for="(message, i) in messages" :key="i">{{ message }}</div>
</div>
</template>

Now, you're listening for broadcasts on the chat/1/1 channel. Any messages broadcast to the chat/1/1 channel will be received by the onMessage callback.