Frequently Asked Questions

How do I use regexes for channel names in handler definitions?

Instead of passing a string as the $channel_name argument of on_join, on_leave, on_action, on_request and on_cleanup, you can pass an arrayref containing channel name path segments, some of which may be regexes.

Examples below:

# matches channel:name
$app->bs->on_join('channel:name', ...);

# matches channel:name
$app->bs->on_join(['channel', 'name'], ...);

# matches channel:15
$app->bs->on_join(['channel', qr/^\d+$/], ...);

# matches channel:name:15
$app->bs->on_join(['channel:name', qr/^\d+$/], ...);

# matches channel:name:15:chat
$app->bs->on_join(['channel:name', qr/^\d+$/, 'chat'], ...);

You can replace $channel_name with just a regex, too. Regexes will not match the colon character.

# matches chess
$app->bs->on_join(qr/^(chess|checkers)$/, ...);

# matches chess:15
$app->bs->on_join([qr/^(chess|checkers)$/, qr/^\d+$/], ...);

What happens if you join a channel a second time without having left before?

This can happen for instance when a webpage contains two copies of the same component, each of which joins the channel.

The subsequent join call is equivalent to joining a channel whose join handler is just:

sub {
  return { limit => 0 };
}

I.e., authorization will pass automatically (provided the first join succeeded), state will be received upon succesful join, the new channel object's initialState and state events will fire, future events will come in (using no extra bandwidth), but the backlog of recent events will be empty.

Similar to how a channel's join handler only gets executed on the first join, its leave handler will only get executed on the last leave, or of course when the client disconnects.

What are some ways to secure an app that uses BoardStreams?

To prevent cross-site request forgery, you can check the Origin: header during connection to the websocket server.

Example:

# in the controller class

sub ws ($self) {
  return $self->rendered(401)
    if $self->req->headers->origin ne $self->req->url->base;

  $self->bs->initialize_client;
}

Or alternatively, to allow bots in that have no Origin header:

sub ws ($self) {
  return $self->rendered(401)
    if $self->req->headers->origin and $self->req->headers->origin ne $self->req->url->base;

  $self->bs->initialize_client;
}

What happens if postgres goes down?

The moment a hypnotoad worker detects that it has lost the connection to postgres, it will stop gracefully and kick all clients to let them connect through a different worker process. The worker process will attempt to execute the leave handlers for those clients, however if they containlock_state statements, they will not be able to be executed because postgres is down, and will leave the channel's state in an inconsistent state.

This can happen quite often, for example during a postgresql restart, a server restart, and also during development it's possible that the leave handlers don't get a chance to be executed before a morbo restart.

That's where cleanup handlers come into play: global_cleanup is executed periodically (see cleanup_interval) to check whether a worker is missing. If it finds one, the appropriate cleanup handlers of the channels the worker was using will be called.

So when a leave handler contains lock_state statements, always create a cleanup handler for that channel as well. See guard count .

What are appropriate settings for hypnotoad's systemd unit file?

These are important lines:

[Unit]
...
Requires=network.target postgresql.service
After=network.target postgresql.service
StartLimitBurst=10

[Service]
...
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Without the StartLimitBurst and RestartSec options, if postgresql is down during hypnotoad's start, the service will restart so fast that the max burst limit will be exceeded and systemd would stop trying to restart the service after a certain number of tries.

Can I use BoardStreams with EV?

Unfortunately, BoardStreams doesn't play well with EV. Frequent disconnections occur.