Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions features/custom-data.feature
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Feature: customData on lobbies can be used for filtering and extra information
"""
And "yellow" receives the network event "lobby" with the argument "19yrzmetd2bn7"

When "blue" requests lobbies with this filter:
When "blue" requests lobbies with:
"""json
{
"status": "open"
Expand All @@ -104,7 +104,7 @@ Feature: customData on lobbies can be used for filtering and extra information
}
"""

When "blue" requests lobbies with this filter:
When "blue" requests lobbies with:
"""json
{
"status": "open"
Expand Down
48 changes: 40 additions & 8 deletions features/lobbies.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ Feature: Lobby Discovery

Scenario: List empty lobby set
Given "green" is connected as "1u8fw4aph5ypt" and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884"
When "green" requests all lobbies
When "green" requests lobbies with:
"""json
{}
"""
Then "green" should receive 0 lobbies

Scenario: Don't list lobbies from a different game
Given "green" creates a network for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884"
And "blue" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "yellow" is connected as "19yrzmetd2bn7" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "blue,yellow" are joined in a lobby
When "green" requests all lobbies
When "green" requests lobbies with:
"""json
{}
"""
Then "green" should receive 0 lobbies

Scenario: List lobbies that exist
Expand All @@ -29,7 +35,10 @@ Feature: Lobby Discovery
"""
And "blue" receives the network event "lobby" with the argument "19yrzmetd2bn7"

When "green" requests all lobbies
When "green" requests lobbies with:
"""json
{}
"""
Then "green" should have received only these lobbies:
| code | playerCount |
| 19yrzmetd2bn7 | 1 |
Expand All @@ -54,7 +63,10 @@ Feature: Lobby Discovery
"""
And "yellow" receives the network event "lobby" with the argument "prb67ouj837u"

When "green" requests all lobbies
When "green" requests lobbies with:
"""json
{}
"""
Then "green" should have received only these lobbies:
| code | playerCount | public |
| 3t3cfgcqup9e | 1 | true |
Expand All @@ -74,7 +86,7 @@ Feature: Lobby Discovery
| 8qva9vyurwbbl | 54fa57d5-b4bd-401d-981d-2c13de99be27 | 9 | true |
| 9qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 10 | true |

When "green" requests lobbies with this filter:
When "green" requests lobbies with:
"""json
{
"playerCount": {
Expand All @@ -97,7 +109,7 @@ Feature: Lobby Discovery
| 1qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 1 | {"map": "de_dust"} | true | 2020-01-02 |
| 2qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 1 | {"map": "de_nuke"} | true | 2020-01-03 |

When "green" requests lobbies with this filter:
When "green" requests lobbies with:
"""json
{
"map": "de_nuke",
Expand Down Expand Up @@ -126,7 +138,10 @@ Feature: Lobby Discovery
When "blue" disconnects
Then "blue" receives the network event "close"

When "green" requests all lobbies
When "green" requests lobbies with:
"""json
{}
"""
Then "green" should have received only these lobbies:
| code | playerCount |
| HC6Y | 0 |
Expand All @@ -152,7 +167,7 @@ Feature: Lobby Discovery
"""
And "green" receives the network event "lobby" with the argument "19yrzmetd2bn7"

When "blue" requests lobbies with this filter:
When "blue" requests lobbies with:
"""json
{
"map": "de_nuke"
Expand All @@ -162,3 +177,20 @@ Feature: Lobby Discovery
| code |
| 19yrzmetd2bn7 |
| 3qva9vyurwbb |

Scenario: Sort lobbies with a custom order
Given "green" is connected as "1u8fw4aph5ypt" and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884"
And these lobbies exist:
| code | game | playerCount | public | created_at |
| 1qva9vyurwbb | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 1 | true | 2020-01-03 |
| 2qva9vyurwbb | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 3 | true | 2020-01-02 |
| 3qva9vyurwbb | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | 5 | true | 2020-01-01 |

When "green" requests lobbies with:
| filter | {} |
| sort | { "playerCount": -1 } |
| limit | 2 |
Then "green" should have received only these lobbies:
| code | playerCount |
| 3qva9vyurwbb | 5 |
| 2qva9vyurwbb | 3 |
4 changes: 2 additions & 2 deletions features/maxPlayers.feature
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Feature: Lobbies can have a maximum number of players
"""
And "yellow" receives the network event "lobby" with the argument "h5yzwyizlwao"

When "yellow" requests lobbies with this filter:
When "yellow" requests lobbies with:
"""json
{}
"""
Expand All @@ -58,7 +58,7 @@ Feature: Lobbies can have a maximum number of players
}
"""

When "yellow" requests lobbies with this filter:
When "yellow" requests lobbies with:
"""json
{}
"""
Expand Down
4 changes: 2 additions & 2 deletions features/password.feature
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Feature: Lobbies can be password protected
"""
And "yellow" receives the network event "lobby" with the argument "h5yzwyizlwao"

When "yellow" requests lobbies with this filter:
When "yellow" requests lobbies with:
"""json
{}
"""
Expand All @@ -76,7 +76,7 @@ Feature: Lobbies can be password protected
"password": ""
}
"""
And "yellow" requests lobbies with this filter:
And "yellow" requests lobbies with:
"""json
{}
"""
Expand Down
22 changes: 13 additions & 9 deletions features/support/steps/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,21 +210,25 @@ When('{string} leaves the lobby', async function (this: World, playerName: strin
await player.network.leave()
})

When('{string} requests all lobbies', async function (this: World, playerName: string) {
When('{string} requests lobbies with:', async function (this: World, playerName: string, payload: string | DataTable) {
const player = this.players.get(playerName)
if (player == null) {
throw new Error('no such player')
}
const lobbies = await player.network.list()
player.lastReceivedLobbies = lobbies
})
let filter: any
let sort: Record<string, 1 | -1> | undefined
let limit: number | undefined

When('{string} requests lobbies with this filter:', async function (this: World, playerName: string, filter: string) {
const player = this.players.get(playerName)
if (player == null) {
throw new Error('no such player')
if (typeof payload !== 'string') {
const argsHash = payload.rowsHash()
filter = argsHash.filter != null ? JSON.parse(argsHash.filter) : undefined
sort = argsHash.sort != null ? JSON.parse(argsHash.sort) : undefined
limit = argsHash.limit != null ? parseInt(argsHash.limit, 10) : undefined
} else {
filter = JSON.parse(payload)
}
const lobbies = await player.network.list(JSON.parse(filter))

const lobbies = await player.network.list(filter, sort, limit)
player.lastReceivedLobbies = lobbies
})

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/jackc/pgx/v5 v5.7.6
github.com/koenbollen/logging v0.0.0-20230520102501-e01d64214504
github.com/ory/dockertest/v3 v3.12.0
github.com/poki/mongodb-filter-to-postgres v1.0.6
github.com/poki/mongodb-filter-to-postgres v1.0.7
github.com/rs/cors v1.11.1
github.com/rs/xid v1.6.0
go.uber.org/zap v1.27.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/poki/mongodb-filter-to-postgres v1.0.6 h1:K33b8bng07GewuyTipTzVY5qjLVC3q+cURDmklzSLdc=
github.com/poki/mongodb-filter-to-postgres v1.0.6/go.mod h1:AccQTAURp16s/pIp9pTuVqY64kyDJ5Dre4fNA1efO6A=
github.com/poki/mongodb-filter-to-postgres v1.0.7 h1:PH6zGVAEptv0nH/eo4Q+ZRLqc/51M3Giihmmq/hY7wE=
github.com/poki/mongodb-filter-to-postgres v1.0.7/go.mod h1:AccQTAURp16s/pIp9pTuVqY64kyDJ5Dre4fNA1efO6A=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
Expand Down
9 changes: 5 additions & 4 deletions internal/signaling/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ func (p *Peer) HandleListPacket(ctx context.Context, packet ListPacket) error {
if p.ID == "" {
return fmt.Errorf("peer not connected")
}
lobbies, err := p.store.ListLobbies(ctx, p.Game, packet.Filter)
lobbies, err := p.store.ListLobbies(ctx, p.Game, packet.Filter, packet.Sort, packet.Limit)
if err != nil {
return err
}
Expand Down Expand Up @@ -462,13 +462,14 @@ func (p *Peer) HandleJoinPacket(ctx context.Context, packet JoinPacket) error {

err := p.store.JoinLobby(ctx, p.Game, packet.Lobby, p.ID, packet.Password)
if err != nil {
if err == stores.ErrNotFound {
switch err {
case stores.ErrNotFound:
util.ReplyError(ctx, p.conn, util.ErrorWithCode(err, "lobby-not-found"))
return nil
} else if err == stores.ErrInvalidPassword {
case stores.ErrInvalidPassword:
util.ReplyError(ctx, p.conn, util.ErrorWithCode(err, "invalid-password"))
return nil
} else if err == stores.ErrLobbyIsFull {
case stores.ErrLobbyIsFull:
util.ReplyError(ctx, p.conn, util.ErrorWithCode(err, "lobby-is-full"))
return nil
}
Expand Down
29 changes: 24 additions & 5 deletions internal/signaling/stores/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,19 +312,38 @@ func (s *PostgresStore) GetLobby(ctx context.Context, game, lobbyCode string) (L
return lobby, nil
}

func (s *PostgresStore) ListLobbies(ctx context.Context, game, filter string) ([]Lobby, error) {
func (s *PostgresStore) ListLobbies(ctx context.Context, game, filter, sort string, limit int) ([]Lobby, error) {
// TODO: Remove this.
if filter == "" {
filter = "{}"
}

where, values, err := s.filterConverter.Convert([]byte(filter), 2)
where, values, err := s.filterConverter.Convert([]byte(filter), 3)
Comment thread
erikdubbelboer marked this conversation as resolved.
if err != nil {
logger := logging.GetLogger(ctx)
logger.Warn("failed to convert filter", zap.String("filter", filter), zap.Error(err))
return nil, fmt.Errorf("invalid filter: %w", err)
}

var order string
if sort != "" {
order, err = s.filterConverter.ConvertOrderBy([]byte(sort))
if err != nil {
logger := logging.GetLogger(ctx)
logger.Warn("failed to convert order", zap.String("sort", sort), zap.Error(err))
return nil, fmt.Errorf("invalid order: %w", err)
}
}
if order == "" {
order = `"createdAt" DESC, "code" ASC`
} else {
order += `, "createdAt" DESC, "code" ASC`
}

if limit <= 0 {
limit = 50
}
Comment thread
erikdubbelboer marked this conversation as resolved.

var lobbies []Lobby
rows, err := s.DB.Query(ctx, `
WITH lobbies AS (
Expand All @@ -348,9 +367,9 @@ func (s *PostgresStore) ListLobbies(ctx context.Context, game, filter string) ([
SELECT *
FROM lobbies
WHERE `+where+`
ORDER BY "createdAt" DESC
LIMIT 50
`, append([]any{game}, values...)...)
ORDER BY `+order+`
LIMIT $2
`, append([]any{game, limit}, values...)...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/signaling/stores/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Store interface {
JoinLobby(ctx context.Context, game, lobby, id, password string) error
LeaveLobby(ctx context.Context, game, lobby, id string) error
GetLobby(ctx context.Context, game, lobby string) (Lobby, error)
ListLobbies(ctx context.Context, game, filter string) ([]Lobby, error)
ListLobbies(ctx context.Context, game, filter, sort string, limit int) ([]Lobby, error)

Subscribe(ctx context.Context, callback SubscriptionCallback, game, lobby, peerID string)
Publish(ctx context.Context, topic string, data []byte) error
Expand Down
2 changes: 2 additions & 0 deletions internal/signaling/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type ListPacket struct {
Type string `json:"type"`

Filter string `json:"filter"`
Sort string `json:"sort"`
Limit int `json:"limit"`
}

type LobbiesPacket struct {
Expand Down
7 changes: 5 additions & 2 deletions lib/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,17 @@ export default class Network extends EventEmitter<NetworkListeners> {
}
}

async list (filter?: object): Promise<LobbyListEntry[]> {
async list (filter?: object, sort?: object, limit?: number): Promise<LobbyListEntry[]> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be nice to have better typings here? Enums or something

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not possible, you can filter and sort on any of the fields in the random JSON object that you can store with a lobby.

@ErikSom ErikSom Sep 25, 2025 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. sort

type SortObject = Record<string, -1 | 1>;
const sort: SortObject = { playerCount: -1, leaderboard: 1 };

if (this._closing || this.signaling.receivedID === undefined) {
return []
}
const filterString = (filter != null) ? JSON.stringify(filter) : undefined
const sortString = (sort != null) ? JSON.stringify(sort) : undefined
const reply = await this.signaling.request({
type: 'list',
filter: filterString
filter: filterString,
sort: sortString,
limit
})
if (reply.type === 'lobbies') {
return reply.lobbies
Expand Down
2 changes: 2 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export interface WelcomePacket extends Base {
export interface ListPacket extends Base {
type: 'list'
filter?: string
sort?: string
limit?: number
}

export interface LobbiesPacket extends Base {
Expand Down
Loading