mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 14:46:41 +00:00
Handle ranked play limits in the client (#4602)
## Summary
Client-side handling for the new server-enforced ranked play limits
(infra#427): free players get a lifetime allowance of ranked matches,
then a small daily allowance; subscribers on tiers with the
`unlimitedRanked` entitlement are never limited.
- **Queue rejection**: the matchmaking socket closing with code 1008 and
reason `ranked_limit_reached` now renders a limit-reached view in the
matchmaking modal (message + "Get unlimited ranked" button that opens
the store on the subscriptions tab) and does **not** auto-reconnect.
Auth-failure 1008 (`Invalid session`) is disambiguated by the reason
string and keeps the existing reconnect-with-refreshed-token path; code
1000 ("replaced by another tab") is unchanged. Both 1v1 and 2v2 go
through the same modal, so both queues get identical handling.
- **`/users/@me`**: added required `player.unlimitedRanked: boolean` to
`UserMeResponseSchema`.
- **cosmetics.json**: added required `unlimitedRanked: boolean` to
`SubscriptionSchema`; store subscription tiles now show an "Unlimited
ranked" perk line for tiers that include it.
Display copy avoids hardcoding the limit numbers since the server
constants may be tuned.
## ⚠️ Deploy ordering
Both new schema fields are **required**, so this must ship **after** the
API starts serving them (infra#427). Against the current API,
`/users/@me` parsing fails (players appear logged out) and a catalog
without the subscription field fails the whole cosmetics parse.
## Testing
- Schema tests for both new fields (present / rejected-when-missing) in
`tests/ApiSchemas.test.ts` and `tests/CosmeticSchemas.test.ts`; full
suite, tsc, ESLint, Prettier all pass.
- Verified in the running app (headless Chromium): forced the modal into
the limit-reached state, confirmed the rendered view, and confirmed the
upsell click closes matchmaking and opens the store on the subscriptions
tab.
- Not verifiable locally (needs deployed infra#427): the real close
frame, the midnight-UTC reset, and the perk line against a real catalog
— covered by the handoff QA checklist.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,9 @@ export const UserMeResponseSchema = z.object({
|
||||
player: z.object({
|
||||
publicId: z.string(),
|
||||
adfree: z.boolean(),
|
||||
// True when the player's active subscription tier exempts them from the
|
||||
// free-ranked-play limits.
|
||||
unlimitedRanked: z.boolean(),
|
||||
flares: z.string().array().optional(),
|
||||
achievements: z.object({
|
||||
singleplayerMap: z.array(SingleplayerMapAchievementSchema),
|
||||
|
||||
@@ -353,6 +353,9 @@ export const SubscriptionSchema = CosmeticSchema.extend({
|
||||
priceMonthly: z.number(),
|
||||
dailySoftCurrency: z.number(),
|
||||
dailyHardCurrency: z.number(),
|
||||
// Whether this tier exempts subscribers from the free-ranked-play limits
|
||||
// (advertised on the store tile).
|
||||
unlimitedRanked: z.boolean(),
|
||||
});
|
||||
|
||||
// Schema for resources/cosmetics/cosmetics.json
|
||||
|
||||
Reference in New Issue
Block a user