Resenha - Add Discord-style Voice Rooms to Your Community 🎙

@Falco Hey I’ve hit a few reproducible issues while testing the updated resenha. Sharing findings + the patches I’m running locally, in case they’re useful upstream.

1. Admin UI silently discards room_type stage rooms can never stick
app/controllers/resenha/admin_rooms_controller.rb:65 (room_params) omits :room_type from the permit list, and admin_room_serializer.rb never serializes it.

Every stage assignment made through the admin UI is dropped, rooms create as open and “revert” to open on any later admin edit. Confirmed via production logs; the user-facing controller permits it, only the admin path lost it.

# admin_rooms_controller.rb — add :room_type to the permit list, then:
if permitted.key?(:room_type)
  value = Resenha::Room::ROOM_TYPES[permitted[:room_type].to_s]
  raise Discourse::InvalidParameters.new(:room_type) if value.nil?
  permitted[:room_type] = value
end
# admin_room_serializer.rb — serialize room_type so the form initializes correctly

2. Invalid room_type silently falls back to open
app/controllers/resenha/rooms_controller.rb:575 — ROOM_TYPES[...] || ROOM_TYPE_OPEN.

Any malformed/stale room_type in an otherwise-valid room edit quietly flips a stage room to open. A 400 makes the misbehaving caller visible instead of corrupting the room.

value = Resenha::Room::ROOM_TYPES[permitted[:room_type].to_s]
raise Discourse::InvalidParameters.new(:room_type) if value.nil?
permitted[:room_type] = value

3. Heartbeat resurrects a user who just left (ghost presence)
rooms_controller.rb:234 (heartbeat) unconditionally re-adds presence, so a heartbeat in flight when leave (:220) processes lands second and re-creates the departed user.
The ghost persists until the TTL reap, which doesn’t broadcast — clients show them “in the room” for up to a minute.

Reproduced on device; kick has the identical exposure.

# ParticipantTracker: 15s tombstone
def mark_left(room_id, user_id)  = redis.setex(left_key(room_id, user_id), 15, "1")
def recently_left?(room_id, user_id) = redis.exists?(left_key(room_id, user_id))
# leave/kick → mark_left; join/livekit_token → clear_left; heartbeat:
return head :no_content if Resenha::ParticipantTracker.recently_left?(@room.id, current_user.id)

4. Stale participant_left webhook phantom-kicks a fresh session
livekit_webhooks_controller.rb:37/57 — departures are matched by user identity alone.
On a quick disconnect/rejoin, the superseded session’s participant_left arrives late and expires the new session’s presence (user “kicked” ~3s after rejoining, back ~15s later).
Reproduced on device; the gone_at heuristic can’t distinguish the sessions when the rejoin predates the old session’s disconnect.

# participant_joined → record the live SID
Resenha::ParticipantTracker.set_livekit_sid(room.id, user_id, event.dig("participant", "sid"))
# expire_participant → skip a superseded session's departure
known = Resenha::ParticipantTracker.livekit_sid(room.id, user_id)
return if sid.present? && known.present? && sid != known

5. DeleteRoom 404 after the last leave floods the logs
lib/resenha/livekit/room_service_client.rb:84 warns on any non-200.
The SFU auto-closes a room the moment it empties, so the last leave’s DeleteRoom routinely races it — “requested room does not exist” is the desired end state, not a fault.
It lands in Logster on every last-leave.

elsif method == "DeleteRoom" && response.status == 404
  Rails.logger.debug("[resenha-livekit] DeleteRoom no-op for room #{room.id}: already gone")
  true

I can send a pull request if you wish.

As I said in previous I don’t know your direction in full, but you can have a look at mines as I’ve made a post here → Discourse Desktop Mac App - #10 by nicolsdennis

1 Like