File Coverage

lib/PAGI/Endpoint/WebSocket.pm
Criterion Covered Total %
statement 46 55 83.6
branch 9 14 64.2
condition 1 2 50.0
subroutine 12 14 85.7
pod 3 5 60.0
total 71 90 78.8


line stmt bran cond sub pod time code
1             package PAGI::Endpoint::WebSocket;
2              
3 4     4   513766 use strict;
  4         4  
  4         155  
4 4     4   14 use warnings;
  4         9  
  4         173  
5              
6 4     4   14 use Future::AsyncAwait;
  4         6  
  4         19  
7 4     4   184 use Carp qw(croak);
  4         6  
  4         3362  
8              
9             # Factory class method - override in subclass for customization
10 7     7 1 4937 sub context_class { 'PAGI::Context' }
11              
12             # Encoding: 'text', 'bytes', or 'json'
13 2     2 1 1329 sub encoding { 'text' }
14              
15             sub to_app {
16 5     5 1 362319 my ($class) = @_;
17 5         19 my $context_class = $class->context_class;
18              
19 3     3   115 return async sub {
20 3         5 my ($scope, $receive, $send) = @_;
21              
22 3   50     10 my $type = $scope->{type} // '';
23 3 50       8 croak "Expected websocket scope, got '$type'" unless $type eq 'websocket';
24              
25 3         490 require PAGI::Context;
26 3         16 my $endpoint = $class->new;
27 3         13 my $ctx = $context_class->new($scope, $receive, $send);
28              
29 3         38 await $endpoint->handle($ctx);
30 5         22 };
31             }
32              
33             sub new {
34 4     4 0 177043 my ($class, %args) = @_;
35 4         9 return bless \%args, $class;
36             }
37              
38 3     3 0 4 async sub handle {
39 3         5 my ($self, $ctx) = @_;
40 3         28 my $ws = $ctx->websocket;
41              
42             # Call on_connect if defined
43 3 50       27 if ($self->can('on_connect')) {
44 3         11 await $self->on_connect($ctx);
45             } else {
46             # Default: accept the connection
47 0         0 await $ws->accept;
48             }
49              
50             # Register disconnect callback
51 3 100       144 if ($self->can('on_disconnect')) {
52             $ws->on_close(sub {
53 1     1   2 my ($code, $reason) = @_;
54 1         3 $self->on_disconnect($ctx, $code, $reason);
55 1         5 });
56             }
57              
58             # Handle messages based on encoding
59 3         12 eval {
60 3 100       12 if ($self->can('on_receive')) {
61 1         6 my $encoding = $self->encoding;
62              
63 1 50       3 if ($encoding eq 'json') {
    50          
64 0     0   0 await $ws->each_json(async sub {
65 0         0 my ($data) = @_;
66 0         0 await $self->on_receive($ctx, $data);
67 0         0 });
68             } elsif ($encoding eq 'bytes') {
69 0     0   0 await $ws->each_bytes(async sub {
70 0         0 my ($data) = @_;
71 0         0 await $self->on_receive($ctx, $data);
72 0         0 });
73             } else {
74             # Default: text
75 2     2   2 await $ws->each_text(async sub {
76 2         2 my ($data) = @_;
77 2         5 await $self->on_receive($ctx, $data);
78 1         4 });
79             }
80             } else {
81             # No on_receive, just wait for disconnect
82 2         8 await $ws->run;
83             }
84             };
85 3 50       68 die $@ if $@;
86             }
87              
88             1;
89              
90             __END__