Skip to content

POC Websocket types - #601

Draft
C0D3-M4513R wants to merge 30 commits into
vrchatapi:mainfrom
C0D3-M4513R:feature/websocket
Draft

C0D3-M4513R wants to merge 30 commits into
vrchatapi:mainfrom
C0D3-M4513R:feature/websocket

Conversation

@C0D3-M4513R

@C0D3-M4513R C0D3-M4513R commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Closes: #598

Please note: This bumps the Openapi Spec version to 3.1, which causes some issues with some language generators!

Copilot AI lite review requested due to automatic review settings September 16, 2026 17:47
@C0D3-M4513R
C0D3-M4513R force-pushed the feature/websocket branch 2 times, most recently from 742b44e to 91b5283 Compare September 16, 2026 17:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved critical and moderate schema issues remain in the WebSocket unions, discriminator modeling, and OpenAPI compatibility.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This proof-of-concept adds OpenAPI schemas for WebSocket messages, notifications, and related payloads to support client deserialization.

Changes:

  • Adds wrapper and non-wrapper WebSocket event schemas.
  • Adds notification, content-refresh, user, friend, group, badge, and queue payload schemas.
  • Registers the schemas in the components index.
File summaries
File Review status
openapi/components/schemas/WebsocketWrapperUserUpdate.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperUserLocation.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperUserBadgeUnassigned.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperUserBadgeAssigned.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperResponseNotification.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperNotificationV2Update.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperNotificationV2Delete.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperNotificationV2.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperNotification.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperInstanceQueueReady.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperInstanceQueueJoined.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperGroupRoleUpdated.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperGroupMemberUpdated.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperGroupLeft.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperGroupJoined.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperFriendUpdate.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperFriendOnline.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperFriendOffline.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperFriendLocation.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperFriendDelete.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperFriendAdd.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperFriendActive.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketWrapperContentRefresh.yaml Moderate finding recorded
openapi/components/schemas/WebsocketUserUpdate.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketUserLocation.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketUserBadgeUnassigned.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketUserBadgeAssigned.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketSeeNotification.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketResponseNotification.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketNotificationV2Update.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketNotificationV2Delete.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketNotificationV2.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketNotification.yaml Critical and moderate findings recorded
openapi/components/schemas/WebsocketMessage.yaml Critical and nit findings recorded
openapi/components/schemas/WebsocketInstanceQueueReady.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketInstanceQueueJoined.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketHideNotification.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketGroupRoleUpdated.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketGroupMemberUpdated.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketGroupLeft.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketGroupJoined.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketFriendUpdate.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketFriendOnline.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketFriendOffline.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketFriendLocation.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketFriendDelete.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketFriendAdd.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketFriendActive.yaml Reviewed; no final finding recorded
openapi/components/schemas/WebsocketContentRefresh.yaml Nit recorded
openapi/components/schemas/WebsocketClearNotification.yaml Critical finding recorded
openapi/components/schemas/_index.yaml Nit recorded
Review details

Suppressed comments (7)

openapi/components/schemas/WebsocketMessage.yaml:15

  • These alternatives include WebsocketWrapperNotification.yaml and other wrapper files that are currently empty in this checkout. Adding WebsocketMessage to _index.yaml makes the bundler resolve those references; empty targets are not usable Schema Objects (or become unconstrained branches), so the bundle will fail or the union will be ambiguous. Define each wrapper before referencing it, or remove unimplemented variants.
      when: jsonschema
      then:
        contentSchema:
          oneOf:
            - $ref: ./WebsocketNotification.yaml

openapi/components/schemas/WebsocketMessage.yaml:14

  • This publishes a 27-variant websocket union without a websocket workflow or capture in test/arazzo.yaml. Per the repository's evidence rule, an uncaptured property, type, or variant is a missing test rather than a schema fact; add workflows that record these frames before indexing the union, otherwise generated clients will be constrained by guessed variants.
    x-if:
      when: jsonschema
      then:
        contentSchema:
          oneOf:

openapi/components/schemas/WebsocketMessage.yaml:41

  • This discriminator maps on type, but WebsocketSeeNotification, WebsocketResponseNotification, and WebsocketClearNotification do not define that property. These branches therefore cannot be selected by the discriminator even after the conditional is placed at the intended schema level; add the actual wrapper type to each variant or map only schemas that carry it.
            - $ref: ./WebsocketGroupJoined.yaml
            - $ref: ./WebsocketGroupLeft.yaml
            - $ref: ./WebsocketGroupMemberUpdated.yaml
            - $ref: ./WebsocketGroupRoleUpdated.yaml
          discriminator:

openapi/components/schemas/WebsocketMessage.yaml:13

  • WebsocketSeeNotification.yaml and WebsocketResponseNotification.yaml currently define the same three required string properties. Any content object matching one therefore matches both branches, so this oneOf cannot distinguish the two message types. Add an evidence-backed schema distinction or discriminate at the enclosing message instead.
      then:
        contentSchema:

openapi/components/schemas/WebsocketMessage.yaml:14

  • Even after fixing the indentation, these branches are WebsocketWrapper... objects; for example, WebsocketWrapperContentRefresh already requires its own type and content. Placing that union under the outer message's content models an extra nested wrapper, so make the message itself the union (or make the branches content-only) and discriminate at the field that actually carries the event type.
    x-if:
      when: jsonschema
      then:
        contentSchema:
          oneOf:

openapi/components/schemas/WebsocketNotification.yaml:12

  • The websocket detail union omits NotificationEmpty, even though the existing SentNotification union models {} with that schema and NotificationType includes types such as friendRequest and message that do not have a listed detail shape. Those valid empty-detail cases are left without a branch. Add the existing empty-detail schema and make the other branches mutually exclusive, or explicitly constrain this websocket model to the covered notification types.
        oneOf:

openapi/components/schemas/_index.yaml:14

  • These newly published WebSocket schemas add properties, required entries, enum values, and examples without any Arazzo workflow/capture covering WebSocket frames. Add workflows that capture the relevant message states before publishing these branches; otherwise the schema is inferred rather than evidence-backed.
WebsocketMessage:
  $ref: ./WebsocketMessage.yaml
WebsocketNotification:
  $ref: ./WebsocketNotification.yaml
  • Files reviewed: 7/28 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread openapi/components/schemas/WebsocketClearNotification.yaml Outdated
Comment thread openapi/components/schemas/WebsocketMessage.yaml Outdated
Comment on lines +12 to +16
oneOf:
- $ref: ./NotificationDetailBoop.yaml
- $ref: ./NotificationDetailInvite.yaml
- $ref: ./NotificationDetailInviteResponse.yaml
- $ref: ./NotificationDetailRequestInvite.yaml

@C0D3-M4513R C0D3-M4513R Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This discriminator probably does not map this key properly on at least rust.

Right now I see no simple solution for this though.

Comment thread openapi/components/schemas/WebsocketNotification.yaml
Comment thread openapi/components/schemas/WebsocketContentRefresh.yaml Outdated
@C0D3-M4513R
C0D3-M4513R force-pushed the feature/websocket branch 2 times, most recently from 09c9cf1 to 5b4cd87 Compare September 16, 2026 17:59
@pkg-pr-new

pkg-pr-new Bot commented Sep 16, 2026

Copy link
Copy Markdown
pnpm add https://pkg.pr.new/vrchatapi/specification/vrchat@5b5f3d6

commit: 5b5f3d6

@C0D3-M4513R
C0D3-M4513R requested review from ariesclark and a lite review from Copilot September 16, 2026 18:58
@C0D3-M4513R
C0D3-M4513R marked this pull request as ready for review September 16, 2026 18:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved critical schema, discriminator, compatibility, and component-preservation issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (4)

openapi/components/schemas/Notification.yaml:31

  • These discriminator keys do not match the repository's NotificationType values (inviteResponse, requestInvite, requestInviteResponse, votetokick in NotificationType.yaml:4-11); the mapping uses hyphenated spellings instead. Even after fixing the discriminator placement, those notification variants will not resolve to a branch. Use the actual enum values as mapping keys.
              invite-response: ./NotificationDetailInviteResponse.yaml
              request-invite: ./NotificationDetailRequestInvite.yaml
              request-invite-response: ./NotificationDetailRequestInviteResponse.yaml
              vote-to-kick: ./NotificationDetailVoteToKick.yaml

openapi/components/schemas/WebsocketMessage.yaml:7

  • No Arazzo workflow or captured response accompanies these new WebSocket branches, while this repository derives schema properties, enum values, and required entries from traffic. As written, this publishes guessed shapes (the TODOs in the new schemas reinforce that); add capture-backed workflow coverage and derive the union from it before adding these schemas.
    oneOf:
      - $ref: ./WebsocketMessageNormal.yaml
      - $ref: ./WebsocketMessageDoubleJson.yaml

openapi/components/schemas/WebsocketNotification.yaml:19

  • These oneOf branches are not mutually exclusive: the issue's invite payload has worldId and worldName but no type, and NotificationDetailBoop has no required properties, so it also accepts that object. The oneOf therefore matches both Boop and Invite and rejects the notification instead of fixing deserialization. Make the branches mutually exclusive using capture-backed requirements, or use a non-oneOf representation if the wire payload has no discriminator.
        oneOf:
          - $ref: ./NotificationDetailBoop.yaml
          - $ref: ./NotificationDetailInvite.yaml
          - $ref: ./NotificationDetailInviteResponse.yaml
          - $ref: ./NotificationDetailRequestInvite.yaml
          - $ref: ./NotificationDetailRequestInviteResponse.yaml
          - $ref: ./NotificationDetailVoteToKick.yaml
        discriminator:

openapi/components/schemas/WebsocketNotification.yaml:27

  • These discriminator keys do not match the repository's NotificationType values (inviteResponse, requestInvite, requestInviteResponse, votetokick in NotificationType.yaml:4-11); the mapping uses hyphenated spellings instead. Even after fixing the discriminator placement, those notification variants will not resolve to a branch. Use the actual enum values as mapping keys.
            invite-response: ./NotificationDetailInviteResponse.yaml
            request-invite: ./NotificationDetailRequestInvite.yaml
            request-invite-response: ./NotificationDetailRequestInviteResponse.yaml
            vote-to-kick: ./NotificationDetailVoteToKick.yaml
  • Files reviewed: 33/33 changed files
  • Comments generated: 19
  • Review effort level: Lite

Comment on lines +23 to +27
discriminator:
propertyName: type
mapping:
boop: ./NotificationDetailBoop.yaml
invite: ./NotificationDetailInvite.yaml
Comment thread openapi/components/schemas/WebsocketContentRefresh.yaml Outdated
Comment thread openapi/components/schemas/WebsocketGroupJoined.yaml Outdated
Comment thread openapi/components/schemas/WebsocketGroupLeft.yaml Outdated
Comment thread openapi/components/schemas/WebsocketGroupMemberUpdated.yaml Outdated
Comment thread openapi/components/schemas/WebsocketInstanceQueueReady.yaml Outdated
Comment on lines +7 to +11
- notification
- response-notification
- notification-v2
- notification-v2-update
- notification-v2-delete
Comment thread openapi/components/schemas/WebsocketContentRefresh.yaml Outdated
Comment thread openapi/components/schemas/WebsocketContentRefreshContentType.yaml Outdated
Comment thread openapi/components/schemas/WebsocketNotificationV2Update.yaml Outdated
@C0D3-M4513R

Copy link
Copy Markdown
Contributor Author

This approach does not work for actually deserializing the double-json encoded json, sadly.

Also the discriminator mappings do not work (at least in rust).

If possible I'd like someone more skilled at Openapi spec to look at this.

First search: `\n(\s*)type: (.*)\n\1nullable: true`
First replace: `\n$1type: ['null', '$2']`

Second search: `\n(\s*)type: (.*)\n\1description:(.*)\n\1nullable: true`
Second replace: `\n$1type: ['null', '$2']\n$1description:$3`
Created using `for i in WebsocketNotification.yaml WebsocketResponseNotification.yaml WebsocketNotificationV2Update.yaml WebsocketNotificationV2Delete.yaml WebsocketFriendAdd.yaml WebsocketFriendDelete.yaml WebsocketFriendOnline.yaml WebsocketFriendActive.yaml WebsocketFriendOffline.yaml WebsocketFriendUpdate.yaml WebsocketFriendLocation.yaml WebsocketUserUpdate.yaml WebsocketUserLocation.yaml WebsocketUserBadgeAssigned.yaml WebsocketUserBadgeUnassigned.yaml WebsocketContentRefresh.yaml WebsocketInstanceQueueJoined.yaml WebsocketInstanceQueueReady.yaml WebsocketGroupJoined.yaml WebsocketGroupLeft.yaml WebsocketGroupMemberUpdated.yaml WebsocketGroupRoleUpdated.yaml; do echo -e "title: ${i%.*}Encoded\ntype: object\nproperties:\n  content:\n    type: string\n    contentMediaType: application/json\n    contentSchema:\n      \$ref: ./${i}\nrequired:\n  - content" > ${i%.*}Encoded.yaml; done`
@C0D3-M4513R

Copy link
Copy Markdown
Contributor Author

So the descriminator mappings work now.
However json-encoded string deserialization doesn't seem to be supported.

Therefore, due to how at least Rust enums work, I had to create one type with the suffix "Encoded" for each Websocket double-encoded type.

@C0D3-M4513R
C0D3-M4513R marked this pull request as draft September 17, 2026 00:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants