File Coverage

lib/PAGI/Middleware/SSE/Retry.pm
Criterion Covered Total %
statement 39 40 97.5
branch 7 10 70.0
condition 7 12 58.3
subroutine 7 7 100.0
pod 1 1 100.0
total 61 70 87.1


line stmt bran cond sub pod time code
1             package PAGI::Middleware::SSE::Retry;
2              
3 2     2   203101 use strict;
  2         5  
  2         74  
4 2     2   11 use warnings;
  2         3  
  2         103  
5 2     2   437 use parent 'PAGI::Middleware';
  2         385  
  2         11  
6 2     2   144 use Future::AsyncAwait;
  2         3  
  2         13  
7              
8             =head1 NAME
9              
10             PAGI::Middleware::SSE::Retry - Add retry hints to SSE events
11              
12             =head1 SYNOPSIS
13              
14             use PAGI::Middleware::Builder;
15              
16             my $app = builder {
17             enable 'SSE::Retry',
18             retry => 5000; # 5 seconds
19             $my_app;
20             };
21              
22             =head1 DESCRIPTION
23              
24             PAGI::Middleware::SSE::Retry adds the C field to SSE events,
25             telling clients how long to wait before reconnecting after a disconnect.
26              
27             =head1 CONFIGURATION
28              
29             =over 4
30              
31             =item * retry (default: 3000)
32              
33             Default retry interval in milliseconds.
34              
35             =item * include_on_start (default: 1)
36              
37             If true, sends a retry hint immediately after sse.start.
38              
39             =item * include_on_events (default: 0)
40              
41             If true, includes retry in every sse.send event.
42              
43             =back
44              
45             =cut
46              
47             sub _init {
48 3     3   9 my ($self, $config) = @_;
49              
50 3   100     15 $self->{retry} = $config->{retry} // 3000;
51 3   100     16 $self->{include_on_start} = $config->{include_on_start} // 1;
52 3   50     17 $self->{include_on_events} = $config->{include_on_events} // 0;
53             }
54              
55             sub wrap {
56 3     3 1 35 my ($self, $app) = @_;
57              
58 3     3   62 return async sub {
59 3         56 my ($scope, $receive, $send) = @_;
60             # Only apply to SSE connections
61 3 100       11 if ($scope->{type} ne 'sse') {
62 1         5 await $app->($scope, $receive, $send);
63 1         189 return;
64             }
65              
66 2         4 my $retry = $self->{retry};
67 2         4 my $sent_initial = 0;
68              
69             # Wrap send to add retry field
70 3         39 my $wrapped_send = async sub {
71 3         4 my ($event) = @_;
72 3 100       7 if ($event->{type} eq 'sse.start') {
73 2         4 await $send->($event);
74              
75             # Send initial retry hint
76 2 50 33     147 if ($self->{include_on_start} && !$sent_initial) {
77 2         3 $sent_initial = 1;
78 2         14 await $send->({
79             type => 'sse.send',
80             retry => $retry,
81             });
82             }
83 2         70 return;
84             }
85              
86 1 50       3 if ($event->{type} eq 'sse.send') {
87             # Add retry to event if configured and not already present
88 1 50 33     3 if ($self->{include_on_events} && !defined $event->{retry}) {
89 0         0 $event = { %$event, retry => $retry };
90             }
91             }
92              
93 1         3 await $send->($event);
94 2         7 };
95              
96             # Add retry info to scope
97 2         9 my $new_scope = {
98             %$scope,
99             'pagi.sse.retry' => $retry,
100             };
101              
102 2         4 await $app->($new_scope, $receive, $wrapped_send);
103 3         16 };
104             }
105              
106             1;
107              
108             __END__