Skip to content

Broadcasting

Broadcast PostPublished through the normal event dispatcher:

await Event.dispatch(PostPublished(postId: 42, title: 'Hello Maat'));

Broadcast events to public, private, or presence channels through a configured Pusher-compatible server. Add BroadcastServiceProvider to your application’s providers, then configure a connection and declare the channels clients may join.

Broadcast Event Contract

class PostPublished
implements ShouldBroadcast, BroadcastsAs, BroadcastsWith, BroadcastsWhen {
PostPublished({required this.postId, required this.title});
final int postId;
final String title;
@override
List<Channel> broadcastOn() => [PrivateChannel('posts.$postId')];
@override
String broadcastAs() => 'PostPublished';
@override
Map<String, Object?> broadcastWith() => {'id': postId, 'title': title};
@override
bool broadcastWhen() => title.isNotEmpty;
}

Implement ShouldBroadcast to make an event broadcastable. BroadcastsAs, BroadcastsWith, and BroadcastsWhen are optional: without them the event’s runtime type is its name, its payload is empty, and it is sent.

Channel Types

Use Channel for public channels, PrivateChannel for authenticated channels, and PresenceChannel for authenticated channels that also share member data. Prefer names without private- or presence-; the channel type adds the wire prefix. Already-prefixed names are also accepted and keep exactly one prefix.

Authorizing private and presence channels

Register unprefixed patterns with Broadcast.channel. The built-in POST /broadcasting/auth endpoint authenticates the configured guard, strips the private or presence prefix before matching, and returns 403 when the user, pattern, or authorizer denies access.

Broadcast.channel('posts.{id}', (user, params) async {
return user.authIdentifier.toString() == params['id'];
});
Broadcast.channel('chat.{roomId}', (user, params) async {
return {'name': 'Member ${user.authIdentifier}'};
});

Return true for an allowed private channel. Return a Map<String, Object?> for an allowed presence channel; it becomes that member’s data. Return false or null to deny either kind.

maat make:channel PostsChannel creates routes/posts_channel.dart with a deny-by-default authorizer. Replace its TODO with application-specific ownership logic before calling registerPostsChannel() while booting your application.

Driver Configuration

Use this exact config/broadcasting.dart shape:

Map<String, dynamic> get broadcasting => {
'default': env('BROADCAST_CONNECTION', 'log'),
'connections': {
'pusher': {
'driver': 'pusher',
'key': env('PUSHER_APP_KEY', ''),
'secret': env('PUSHER_APP_SECRET', ''),
'app_id': env('PUSHER_APP_ID', ''),
'host': env('PUSHER_HOST', '127.0.0.1'),
'port': envInt('PUSHER_PORT', 6001),
'scheme': env('PUSHER_SCHEME', 'http'),
},
'log': {'driver': 'log'},
'null': {'driver': 'null'},
},
};

pusher signs HTTP publishes for Pusher Protocol v7 backends such as Thoth. Use log during development to inspect broadcasts, or null to discard them. Connections are created and cached when first selected, so the blank Pusher credentials above do not break the default log connection.

Dispatching Events

Dispatch the event as usual; listeners run before the broadcast:

await Event.dispatch(PostPublished(postId: 42, title: 'Hello Maat'));

You can also send an anonymous event without creating a class:

await Broadcast.on('posts')
.as('PostPublished')
.with_({'id': 42, 'title': 'Hello Maat'})
.send();

To omit the browser that caused an update, pass its request to toOthers. Maat reads X-Socket-ID first, then the _socket_id form field.

await broadcast(
PostPublished(postId: 42, title: 'Hello Maat'),
).toOthers(request).send();

Inspecting Channels

List registered authorization patterns in registration order:

Terminal window
maat channel:list

Use Thoth’s signed inspection endpoints to see live channel occupancy; see WebSockets with Thoth.

Faking Broadcasts

Replace the application’s broadcaster with an in-memory fake and assert only the broadcasts your test cares about:

final broadcasts = Broadcast.fake();
await Broadcast.on('posts').as('PostPublished').with_({'id': 42}).send();
broadcasts.assertBroadcasted(
'PostPublished',
(sent) => sent.channels.contains('posts') && sent.payload['id'] == 42,
);
broadcasts.assertBroadcastedTimes('PostPublished', 1);
broadcasts.assertNotBroadcasted('PostArchived');

Use assertNothingBroadcasted() when a test expects no broadcast at all.