Examples

Counter

Try switching your browser to offline mode for a while, to see up to 4 missed events appear when you turn it back on again.

Vue code (client):

<template>
  <div>
    <div class="panel">
      <div class="left pane">
        <div class="header">
          <strong>Events</strong>
        </div>
        <div ref="eventsData" class="data">
          <div v-for="(event, idx) in events" :key="idx">
            {{ event }}
          </div>
        </div>
      </div>
      <div class="right pane">
        <div class="header">
          <strong>State</strong>
        </div>
        <div class="data">
          {{ state }}
        </div>
      </div>
    </div>

    <div class="form">
      <input
        v-model.number="number"
        type="number"
        placeholder="Enter number..."
      >

      <button @click="incBy(number)">
        Increase by...
      </button>
      <button @click="setTo(number)">
        Set to... (after 3 seconds)
      </button>
    </div>
  </div>
</template>

<script>
import BS from '@/libs/BS'

export default {
  data: () => ({
    state: null,
    events: [],
    number: null
  }),
  created () {
    this.ch = BS.joinChannel('counter')

    this.ch.on('event', async (ev) => {
      this.events.push(ev)
      if (this.events.length > 20) { this.events.shift() }
      // scroll to bottom
      await this.$nextTick()
      this.$refs.eventsData.scrollTo(0, this.$refs.eventsData.scrollHeight)
    })

    this.ch.on('state', (state) => {
      this.state = state
    })
  },
  beforeDestroy () {
    this.ch.leave()
  },
  methods: {
    incBy (number) {
      this.ch.doAction('inc_counter', number)
    },
    async setTo (number) {
      try {
        await this.ch.doRequest('set_counter', number)
      } catch (e) {
        window.console.error(e)
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.panel {
  border: 3px solid black;
  display: flex;
  height: 200px;
}

.pane {
  flex: 1 0 0;
  display: flex;
  flex-direction: column;
}

.left {
  border-right: 3px solid black;
}

.header {
  border-bottom: 3px solid black;
  padding: 3px 5px;
}

.data {
  flex: 1 0 0;
  overflow-y: auto;
  padding: 3px 5px;
}

.form {
  margin-top: 1em;
}
</style>

Perl code (server):

package MyApp::Plugin::DemoCounter;

use Mojo::Base 'Mojolicious::Plugin', -signatures, -async_await;

use Mojo::JSON 'true';
use Mojo::Promise;
use Mojo::IOLoop;

use constant CHANNEL => 'counter';
use constant PASSWORD => '...';

sub register ($self, $app, $config) {
    $app->bs->create_channel(CHANNEL, 1);

    $app->bs->on_join(CHANNEL, sub ($channel_name, $c, $attrs) {
        my $is_reconnect = $attrs->{is_reconnect};
        return { limit => $is_reconnect ? 4 : 0 };
    });

    $app->bs->on_action(CHANNEL, 'inc_counter', sub ($channel_name, $c, $payload) {
        $c->bs->lock_state(CHANNEL, sub ($state) {
            $state += $payload;
            my $event = "Someone increased the counter by $payload";

            return $event, $state;
        });
    });

    $app->bs->on_request(CHANNEL, 'set_counter', sub ($channel_name, $c, $payload) {
        my $p = Mojo::Promise->new;

        Mojo::IOLoop->timer(3, sub {
            if (1 <= $payload <= 100) {
                $c->bs->lock_state(CHANNEL, sub ($state) {
                    $state = $payload;
                    my $event = "Someone set the counter to $payload";

                    return $event, $state;
                });
                $p->resolve(true);
            } else {
                $p->reject('out of bounds'); # could be a JSON object too
            }
        });

        return $p;
    });

    $app->bs->on_action(CHANNEL, 'inc_by_1', sub ($channel_name, $c, $payload) {
        my $password = $payload;
        die if $password ne PASSWORD;

        $c->bs->lock_state(CHANNEL, sub ($state) {
            $state += 1;
            $state = 1 if $state > 100;

            return $state, $state;
        });
    });
}

1;

Perl script (server):

This script increases the counter every two seconds.

#!/usr/bin/env perl

use v5.32;
use warnings;

use BoardStreams::Client;
use Mojo::IOLoop;

use constant PASSWORD => '...';

my $BS = BoardStreams::Client->new($ENV{WEBSOCKET_URL});
my $ch = $BS->join_channel('counter');

Mojo::IOLoop->recurring(2, sub {
    $ch->do_action('inc_by_1', PASSWORD);
});

Mojo::IOLoop->start;

Cron script (server):

This script deletes old events from the database.

#!/usr/bin/env perl

use v5.32;
use warnings;

use Sys::RunAlone silent => 1;

use MyApp;

my $app = MyApp->new;

$app->bs->delete_events('counter', keep_num => 10);

__END__