diff --git a/Dockerfile b/Dockerfile index c8dd4cc45dfc8c482218aff9c9bfbc091bcebbc2..97dbe955141448dfec673910fcc815d178eed19d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN cpanm \ Mojo::Redis \ Mojo::JWT \ Mojolicious::Plugin::OpenAPI \ + Mojolicious::Plugin::SentrySDK \ Mojolicious::Plugin::SwaggerUI ADD . /opt/cf diff --git a/lib/CF/Controller/Websockets.pm b/lib/CF/Controller/Websockets.pm index 7c821e8d0d8f2c961094976045480d733eef49bc..91f5911f647b4d6b51b84a7593328f3734e45384 100644 --- a/lib/CF/Controller/Websockets.pm +++ b/lib/CF/Controller/Websockets.pm @@ -3,128 +3,71 @@ package CF::Controller::Websockets; use Mojo::Base 'Mojolicious::Controller'; use Mojo::Pg::PubSub; use Digest::SHA qw(hmac_sha1_hex); -use POSIX qw(ceil); use Encode qw(encode decode); use feature 'signatures'; no warnings qw{ experimental::signatures }; use constant SOCKET_INACTIVITY_TIMEOUT => 180; +use constant ALIVE_TIME => 60; sub main { my $c = shift; - my $ip = $c->tx->remote_address; + $c->inactivity_timeout(SOCKET_INACTIVITY_TIMEOUT); + $c->tx->with_compression; + my $key = $c->req->headers->header('Sec-WebSocket-Key'); - $c->inactivity_timeout(SOCKET_INACTIVITY_TIMEOUT); + my $pubsub = $c->redis->pubsub; - if ($c->req->headers->header('Sec-WebSocket-Extensions') =~ /permessage-deflate/) { - $c->tx->compressed(1); - $c->res->headers->add('Sec-WebSocket-Extensions' => 'permessage-deflate'); - } - - my $pubsub; - - if ($c->cfg->{message_broker} eq 'redis') { - $pubsub = $c->redis->pubsub; - $pubsub->listen(notify => sub($pubsub, $payload) { - $c->send(decode("UTF-8", $payload)); - }); - } - else { - $pubsub = Mojo::Pg::PubSub->new(pg => $c->pg); - $pubsub->listen(notify => sub($pubsub, $payload) { - $c->send($payload); - }); - } + my $listener = $pubsub->listen(notify => sub($pubsub, $payload) { + $c->send(decode("UTF-8", $payload)); + }); $c->on(json => sub( $c, $message ) { if ( $message->{event} eq 'KEEPALIVE' ) { my $user; + my $is_member; if ($message->{payload} =~ /^\d+$/) { - - $user = $c->pg->db->select('users', - [qw(id username secret jitsi_allowed roles banned_until)], - { id => $message->{payload}} - )->hash; + $user = $c->schema->resultset('User')->find({ + id => $message->{payload}, + }); } if ( $user ) { - my $sig = hmac_sha1_hex($message->{payload}, $user->{secret} ); + my $sig = hmac_sha1_hex($message->{payload}, $user->secret ); if ( $sig ne $message->{sig} ) { $c->app->log->warn( - "Invalid signature for " . $user->{username} + "Invalid signature for " . $user->username ); - $user = undef; } - } - - eval { - my $tx = $c->pg->db->begin; - - $c->pg->db->delete('sockets', [ - {id => $key}, - $user ? {user_id => $user->{id}}:{} - ]); - - $c->pg->db->insert('sockets', { - id => $key, - ip => $ip, - keepalive => \'now()', - user_id => $user ? $user->{id} : undef, - }); + else { + $user->update({ keepalive => \'now()' }); + $is_member = ( $user && $user->roles =~ /member/) ? 1:0; - $tx->commit; - }; + my $jitsi = $user->jitsi_allowed || $user->roles =~ /chairman|jitsi/; - my $all = $c->pg->db->query( - 'select count(*) from sockets_view' - )->hash->{count}; - - my $members = $c->pg->db->query( - 'select count(*) from sockets_view where is_member' - )->hash->{count}; - - my $group_size = $c->_member_group_size($members); - - $c->send({json => { event => 'online_users_updated', payload => { - all => $all, - members => $members, - group_size_full => $group_size->{full}, - group_size_half => $group_size->{half}, - }}}); + $c->send({json => { event => 'user_status', payload => { + jitsi_allowed => $jitsi ? \1:\0, + is_banned => $user->banned_until ? \1:\0, + }}}); + } + } - if ( $user ) { - my $jitsi = $user->{jitsi_allowed} || $user->{roles} =~ /chairman|jitsi/; + $c->redis->db->set( + join (':', ('live', 0, $is_member, $is_member ? $user->id : $key)), #TODO: event_id + 'live', 'EX', ALIVE_TIME + ); - $c->send({json => { event => 'user_status', payload => { - jitsi_allowed => $jitsi ? \1:\0, - is_banned => $user->{banned_until} ? \1:\0, - }}}); - } } }); $c->on(finish => sub ($c, $code, $reason = undef) { - $pubsub->unlisten('notify'); + $pubsub->unlisten('notify', $listener); $c->app->log->debug("WebSocket closed with status $code"); }); } -sub _member_group_size ($c, $total = 0){ - my $group = 2 * sqrt($total); - my $min = $total / 100; - my $max = $total / 5; - - $group = $min if $group < $min; - $group = $max if $group > $max; - - return { - full => ceil( $group ), - half => ceil( $group/2 ), - }; -} - 1; diff --git a/openapi.yaml b/openapi.yaml index f32c79eb130fd35581379fd4cd8d968ad057cb80..b012eb586cdb8a29b7f9b73b79e0efd98b88f55e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: - version: "2.6.1" + version: "2.7.0" title: CF Online description: CF Online license: diff --git a/script/online b/script/online new file mode 100755 index 0000000000000000000000000000000000000000..6ec4ef29dd8244e19b4cf2fcd618293606f13c74 --- /dev/null +++ b/script/online @@ -0,0 +1,69 @@ +#!/usr/bin/perl + +use Mojo::IOLoop; +use Mojo::Redis; +use Mojo::JSON qw(encode_json); +use POSIX qw(ceil); + +use feature 'signatures'; +no warnings qw{ experimental::signatures }; + +my $tick = 0; +my $redis = Mojo::Redis->new($ENV{CFG_REDIS}); +my $id = Mojo::IOLoop->recurring(1 => \&counter); + +Mojo::IOLoop->start unless Mojo::IOLoop->is_running; + +sub counter { + + my $counts = {}; + + $redis->db->keys( 'live:*', sub { + my ($db, $err, $res) = @_; + + KEYS: + foreach my $key ( @{ $res} ) { + my (undef, $event_id, $role) = split /:/, $key; + $counts->{$event_id}{all}++; + $counts->{$event_id}{members}++ if $role; + } + + EVENT: + foreach my $event_id ( keys %{ $counts } ) { + my $all = $counts->{$event_id}{all}; + + # zpomalovac + next EVENT if $tick > 9000/$all; + + my $group_size = member_group_size($all); #TEST + $redis->pubsub->notify( notify => encode_json({ + event => 'online_users_updated', + payload => { + all => $all, + members => $counts->{$event_id}{members}, +# group_size_full => $group_size->{full}, + group_size_half => $group_size->{half}, + tick => $tick, + } + })); + } + }); + + $tick = 0 if ++$tick > 9; +} + +sub member_group_size ($total = 0){ + my $group = 2 * sqrt($total); + my $min = $total / 100; + my $max = $total / 5; + + $group = $min if $group < $min; + $group = $max if $group > $max; + + return { + full => ceil( $group ), + half => ceil( $group/2 ), + }; +} + +1;