@@ -40,8 +40,6 @@ type Data struct {
4040 Term string
4141 // TODO:
4242 Lookup map [string ]string
43- // Pty is the PTY dimension.
44- Pty models.SSHPty
4543 // Handled check if the session is already handling a "shell", "exec" or a "subsystem".
4644 Handled bool
4745}
@@ -156,15 +154,77 @@ type Session struct {
156154
157155 once * sync.Once
158156
159- // Seat is a counter of how many passengers a session has. It's used on the record session feature .
157+ // Seats represents passengers a session.
160158 //
161159 // A passenger is, in a multiplexed SSH session, the subsequent SSH sessions that connect to the same server using
162160 // the already established master connection.
163- Seat * atomic. Int32
161+ Seats Seats
164162
165163 Data
166164}
167165
166+ // Seat represent a passenger in a session.
167+ type Seat struct {
168+ // HasPty is the status of pty on the seat.
169+ HasPty bool
170+ }
171+
172+ type Seats struct {
173+ // counter count atomically seats of a session.
174+ counter * atomic.Int32
175+ // Items represents the individual seat of a session.
176+ Items * sync.Map
177+ }
178+
179+ // NewSeats creates a new [Seats] defining initial values for internal properties.
180+ func NewSeats () Seats {
181+ return Seats {
182+ counter : new (atomic.Int32 ),
183+ Items : new (sync.Map ),
184+ }
185+ }
186+
187+ // NewSeat creates a new seat inside seats.
188+ func (s * Seats ) NewSeat () (int , error ) {
189+ id := int (s .counter .Load ())
190+ defer s .counter .Add (1 )
191+
192+ s .Items .Store (id , & Seat {
193+ HasPty : false ,
194+ })
195+
196+ return id , nil
197+ }
198+
199+ // Get gets a seat reference from their id.
200+ func (s * Seats ) Get (seat int ) (* Seat , bool ) {
201+ loaded , ok := s .Items .Load (seat )
202+ if ! ok {
203+ return nil , false
204+ }
205+
206+ item , ok := loaded .(* Seat )
207+ if ! ok {
208+ return nil , false
209+ }
210+
211+ return item , true
212+ }
213+
214+ // SetPty sets a pty status to a seat from their id.
215+ func (s * Seats ) SetPty (seat int , status bool ) {
216+ item , ok := s .Get (seat )
217+ if ! ok {
218+ log .Warn ("failed to set pty because no seat was created before" )
219+
220+ return
221+ }
222+
223+ item .HasPty = status
224+
225+ s .Items .Store (seat , item )
226+ }
227+
168228// NewSession creates a new Session but differs from [New] as it only creates
169229// the session without registering, connecting to the agent, etc.
170230//
@@ -268,8 +328,8 @@ func NewSession(ctx gliderssh.Context, tunnel *httptunnel.Tunnel, cache cache.Ca
268328 Lookup : lookup ,
269329 SSHID : fmt .Sprintf ("%s@%s.%s" , target .Username , domain , hostname ),
270330 },
271- once : new (sync.Once ),
272- Seat : new (atomic. Int32 ),
331+ once : new (sync.Once ),
332+ Seats : NewSeats ( ),
273333 Agent : & Agent {
274334 Channels : make (map [int ]* AgentChannel ),
275335 },
@@ -403,16 +463,14 @@ func (s *Session) authenticate() error {
403463 })
404464}
405465
406- func (s * Session ) Recorded () error {
466+ func (s * Session ) Recorded (seat int ) error {
407467 value := true
408468
409469 if ! s .Namespace .Settings .SessionRecord {
410470 return errors .New ("record is disable for this namespace" )
411471 }
412472
413- // NOTE: For now, [s.Pty] helps to indicate if the session is recorded.
414- // TODO: After seats refactoring, [pty-req] event on session will indicate if it was recorded.
415- if s .Pty .Columns == 0 && s .Pty .Rows == 0 {
473+ if seat , ok := s .Seats .Get (seat ); ! ok || ! seat .HasPty {
416474 return errors .New ("session won't be recorded because there is no pty" )
417475 }
418476
@@ -578,15 +636,14 @@ func (s *Session) Auth(ctx gliderssh.Context, auth Auth) error {
578636}
579637
580638func (s * Session ) NewSeat () (int , error ) {
581- seat := int (s .Seat .Load ())
582- defer s .Seat .Add (1 )
583-
584- return seat , nil
639+ return s .Seats .NewSeat ()
585640}
586641
587642// Events register an event to the session.
588643func (s * Session ) Event (t string , data any , seat int ) {
589644 if s .Events .Closed () {
645+ log .Debug ("failed to save because events connection was closed" )
646+
590647 return
591648 }
592649
@@ -601,6 +658,8 @@ func (s *Session) Event(t string, data any, seat int) {
601658
602659func Event [D any ](sess * Session , t string , data []byte , seat int ) {
603660 if sess .Events .Closed () {
661+ log .Debug ("failed to save because events connection was closed" )
662+
604663 return
605664 }
606665
@@ -689,21 +748,28 @@ func (s *Session) Finish() (err error) {
689748 "uid" : s .UID ,
690749 }).Info ("saving sessions as Asciinema files" )
691750
692- for seat := range s .Seat .Load () {
693- if err := s .api .SaveSession (s .UID , int (seat )); err != nil {
694- log .WithError (err ).WithFields (log.Fields {
751+ s .Seats .Items .Range (func (key , value any ) bool {
752+ id := key .(int )
753+ seat := value .(* Seat )
754+
755+ if seat .HasPty {
756+ if err := s .api .SaveSession (s .UID , id ); err != nil {
757+ log .WithError (err ).WithFields (log.Fields {
758+ "uid" : s .UID ,
759+ "seat" : seat ,
760+ }).Error ("failed to save the session as Asciinema file" )
761+
762+ return true
763+ }
764+
765+ log .WithFields (log.Fields {
695766 "uid" : s .UID ,
696767 "seat" : seat ,
697- }).Error ("failed to save the session as Asciinema file" )
698-
699- continue
768+ }).Info ("asciinema file saved" )
700769 }
701770
702- log .WithFields (log.Fields {
703- "uid" : s .UID ,
704- "seat" : seat ,
705- }).Info ("asciinema file saved" )
706- }
771+ return true
772+ })
707773 }
708774
709775 log .WithFields (
0 commit comments