Skip to content

feat: Add API versioning support for v2 APIs#510

Draft
KapiWilq wants to merge 2 commits intotosuapp:masterfrom
KapiWilq:feat/api-versioning
Draft

feat: Add API versioning support for v2 APIs#510
KapiWilq wants to merge 2 commits intotosuapp:masterfrom
KapiWilq:feat/api-versioning

Conversation

@KapiWilq
Copy link
Copy Markdown
Contributor

Overlay-side PR: tosuapp/counters#64

This PR introduces API versioning for overlays using v2 and Precise APIs by processing the v query parameter. If the overlay doesn't specify the version it wants to use (or the value is invalid), tosu will send the first version of an API, ensuring backwards compatibility. You can also get JSON response previews by adding the v parameter to the URL, if omitted or invalid - tosu will send the latest version of an API


I also wanted to document this in the wiki, but I guess it's not possible to PR wikis, so if you want to add it, then here:

The wiki article
Starting with tosu X.Y.Z, you can use different versions of v2 and Precise APIs to get more accurate data

## How to use

You can use new versions of APIs in two ways:
1. Using tosu's `socket.js`
2. Using `WebSocket`/`ReconnectingWebSocket` directly

> [!NOTE]
> The API version must be a positive integer. If the value is omitted or invalid (e.g. `-5`, `null`, `"undefined"`, `"abc123"`), the **version 1** of an API will be used. If the version is a fractional number, the decimal component gets truncated (e.g. `3.14159 -> 3` - version 3 will be used)

### Using tosu's `socket.js`

Ensure your `socket.js` is at least version **0.2.0** - if it's an older version, [download the new version](https://github.com/tosuapp/counters/blob/master/quickstart/js/socket.js)

When connecting with either of the APIs, the function signature now looks like this:
```js
socket.api_v2(callback, filters, apiVersion)
socket.api_v2_precise(callback, filters, apiVersion)
```

Callback and [filters](https://github.com/tosuapp/tosu/wiki/Apply-filters-to-websocket) remain the same, `apiVersion` is an optional parameter

### Using `WebSocket`/`ReconnectingWebSocket` directly

Simply add the `v` parameter to the WebSocket URL:
```js
const socket = new ReconnectingWebSocket(`ws://${window.location.host}/websocket/v2?v=6`);
```

## JSON Response Previews

You can get a preview of how the data is structured in different version by simply adding the `v` URL parameter to the URL:
- `/json/v2?v=2`
- `/json/v2/precise?v=2`

> [!NOTE]
> The API version must be a positive integer. If the value is omitted or invalid (e.g. `-5`, `null`, `"undefined"`, `"abc123"`), the **latest version** of an API will be used. If the version is a fractional number, the decimal component gets truncated (e.g. `3.14159 -> 3` - version 3 will be used)

## Version History

### Version 1

Initial version


### Version 2

#### Precise API

`keys` is now an array of keys in both tourney and regular data:
```ts
interface KeyOverlayButton {
    name: string;
    isPressed: boolean;
    count: number;
}
```

Examples:
- osu!catch in osu!(stable)
```json
{
    "currentTime": 0,
    "keys": [
        {
            "name": "L",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "R",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "D",
            "isPressed": false,
            "count": 0
        }
    ],
    "hitErrors": [],
    "tourney": []
}
```
- osu!mania 7K in osu!(lazer)
```json
{
    "currentTime": 0,
    "keys": [
        {
            "name": "B1",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "B2",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "B3",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "B4",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "B5",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "B6",
            "isPressed": false,
            "count": 0
        },
        {
            "name": "B7",
            "isPressed": false,
            "count": 0
        }
    ],
    "hitErrors": [],
    "tourney": []
}
```

@GabuTheDev
Copy link
Copy Markdown
Member

Could you share more information on this view?

Having API breaking changes on a different API version is fine at first glance but the idea of versioning each architectural change from now on is just giving awful DX.
If we're going to approve this, I think a [stable & next] API versions would offer a better DX overall, letting the pp counter developer assume the risk of the next API breaking at any point.

I know osu!api does have some sort of versioning, but I do not think it is feasable for tosu as it's not publicly used, but locally used.
A planned restructuring of the whole API is planned too (maybe with the drop of the whole v1 and v2 namings and sticking to gosu, sc and tosu API namings)

I do not have the word on this though, so: cc @KotRikD @cyperdark

@GabuTheDev GabuTheDev added the area:server The nodejs backend of tosu. label Nov 30, 2025
@cyperdark cyperdark marked this pull request as draft December 4, 2025 08:30
@cyperdark
Copy link
Copy Markdown
Collaborator

i dont think this kind of versioning in buildv2 is good, since it's doesnt have types check for each version

Comment on lines +6 to +9
if (value === undefined || value === null || value.trim() === '')
return fallbackValue;
const num = Number(value);
if (!Number.isFinite(num)) return fallbackValue;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

isRealNumber exists in manipulation.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:server The nodejs backend of tosu.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants