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 config/app.imba or config/app.ts file under resolvers:

config/app.ts
...
import BroadcastServiceResolver from '../app/Resolvers/BroadcastServiceResolver'
...

export default {
...
resolvers: {
...
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: 'default',
expiration: {
mode: 'PX',
ttl: 300,
},
},
...
}

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 Broadcasts

Broadcasts are channels that broadcast messages to other clients. For example, a chat application may broadcast messages to a conversation channel. All clients listening on that channel will receive the message. Broadcasts may be defined using the channel method on the Broadcast class. The channel method accepts two arguments: the channel name and the data that should be broadcast to the channel:

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

Broadcast.channel('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)

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)
})

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/src/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/src/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.