mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Base: Major protocol refactor
As discussed in the #development channel in discord - Switch from using message types to integer op codes - Consolidate op-specific keys into `d` sub-object - Shorten low-level payload keys from `messageType` to `op`, add `d` Other changes: - The WebSocketCloseCode enum has been refactored. It's best to just treat it like it's new - Some performance benefits came along the way. Nothing gamechanging, but notable - Various bug fixes discovered while refactoring
This commit is contained in:
parent
91fadf505f
commit
03e32c8b5e
@ -20,17 +20,17 @@ obs-websocket provides a feature-rich RPC communication protocol, giving access
|
||||
- [Creating an authentication string](#creating-an-authentication-string)
|
||||
- [Enumerations](#enumerations)
|
||||
- [Base message types](#message-types)
|
||||
- [Hello](#hello)
|
||||
- [Identify](#identify)
|
||||
- [Identified](#identified)
|
||||
- [Reidentify](#reidentify)
|
||||
- [Event](#event)
|
||||
- [Request](#request)
|
||||
- [RequestResponse](#requestresponse)
|
||||
- [RequestBatch](#requestbatch)
|
||||
- [RequestBatchResponse](#requestbatchresponse)
|
||||
- [Requests](#requests)
|
||||
- [OpCode 0 Hello](#hello)
|
||||
- [OpCode 1 Identify](#identify)
|
||||
- [OpCode 2 Identified](#identified)
|
||||
- [OpCode 3 Reidentify](#reidentify)
|
||||
- [OpCode 5 Event](#event)
|
||||
- [OpCode 6 Request](#request)
|
||||
- [OpCode 7 RequestResponse](#requestresponse)
|
||||
- [OpCode 8 RequestBatch](#requestbatch)
|
||||
- [OpCode 9 RequestBatchResponse](#requestbatchresponse)
|
||||
- [Events](#events)
|
||||
- [Requests](#requests)
|
||||
|
||||
|
||||
## Connecting to obs-websocket
|
||||
@ -41,44 +41,44 @@ Here's info on how to connect to obs-websocket
|
||||
### Connection steps
|
||||
These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way.
|
||||
|
||||
- Initial HTTP request made to obs-websocket server.
|
||||
- Initial HTTP request made to the obs-websocket server.
|
||||
- HTTP request headers can be used to set the websocket communication type. The default format is JSON. Example headers:
|
||||
- `Content-Type: application/json`
|
||||
- `Content-Type: application/msgpack`
|
||||
- If an invalid `Content-Type` is specified, the connection will be closed with [`WebSocketCloseCode::InvalidContentType`](#websocketclosecode-enum) after upgrade (but before [`Hello`](#hello)).
|
||||
- If an invalid `Content-Type` is specified, the connection will be closed with [`WebSocketCloseCode::InvalidContentType`](#websocketclosecode-enum) after upgrade (but before `Hello`).
|
||||
|
||||
- Once the connection is upgraded, the websocket server will immediately send a [`Hello`](#hello) message to the client.
|
||||
- Once the connection is upgraded, the websocket server will immediately send an [OpCode 0 `Hello`](#hello) message to the client.
|
||||
|
||||
- The client listens for the [`Hello`](#hello) and responds with an [`Identify`](#identify) containing all appropriate session parameters.
|
||||
- The client listens for the `Hello` and responds with an [OpCode 1 `Identify`](#identify) containing all appropriate session parameters.
|
||||
- If there is an `authentication` key in the `messageData` object, the server requires authentication, and the steps in [Creating an authentication string](#creating-an-authentication-string) should be followed.
|
||||
- If there is no `authentication` key, the resulting `Identify` object sent to the server does not require an `authentication` string.
|
||||
- The client determines if the server's `rpcVersion` is supported, and if not it provides its closest supported version in `Identify`.
|
||||
|
||||
- The server receives and processes the [`Identify`](#identify).
|
||||
- If authentication is required and the [`Identify`](#identify) does not contain an `authentication` string, or the string is not correct, the connection is dropped with [`WebSocketCloseCode::AuthenticationFailed`](#websocketclosecode-enum)
|
||||
- If the client has requested an `rpcVersion` which the server cannot use, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum)
|
||||
- The server receives and processes the `Identify` sent by the client.
|
||||
- If authentication is required and the `Identify` message data does not contain an `authentication` string, or the string is not correct, the connection is dropped with [`WebSocketCloseCode::AuthenticationFailed`](#websocketclosecode-enum)
|
||||
- If the client has requested an `rpcVersion` which the server cannot use, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum). This system allows both the server and client to have seamless backwards compatability.
|
||||
- If any other parameters are malformed (invalid type, etc), the connection is dropped with [`WebSocketCloseCode::InvalidIdentifyParameter`](#websocketclosecode-enum)
|
||||
|
||||
- Once identification is processed on the server, the server responds to the client with an [`Identified`](#identified).
|
||||
- Once identification is processed on the server, the server responds to the client with an [OpCode 2 `Identified`](#identified).
|
||||
|
||||
- The client will begin receiving events from obs-websocket and may now make requests to obs-websocket.
|
||||
|
||||
- At any time after a client has been identified, it may send a [`Reidentify`](#reidentify) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification.
|
||||
- At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification.
|
||||
|
||||
#### Connection Notes
|
||||
- The obs-websocket server listens for any messages containing a `request-type` field in the first level JSON from unidentified clients. If a message matches, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum).
|
||||
- If the Content Type is `application/msgpack`, all messages must be sent over binary. If it is `application/json`, all messages must be sent over text.
|
||||
- The obs-websocket server listens for any messages containing a `request-type` field in the first level JSON from unidentified clients. If a message matches, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum) and a warning is logged.
|
||||
- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is dropped with [`WebSocketCloseCode::UnknownMessageType`](#websocketclosecode-enum).
|
||||
- At no point may the client send any message other than a single [`Identify`](#identify) before it has received an [`Identified`](#identified). Breaking this rule will result in the connection being dropped by the server with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum).
|
||||
- The [`Hello`](#hello) object contains an `rpcVersion` field, which is the latest RPC version that the server supports.
|
||||
- If the server's version is is older than the client's, the client is allowed the capability to support older RPC versions. The client determines which RPC version it hopes to communicate on, and sends it via the `rpcVersion` field in the [`Identify`](#identify).
|
||||
- If the server's version is newer than the client's, the client sends its highest supported version in its [`Identify`](#identify) in hopes that the server is backwards compatible to that version.
|
||||
- If the [`Hello`](#hello) does not contain an `authentication` object, the resulting [`Identify`](#identify) object sent to the server does not need to have an `authentication` string.
|
||||
- At no point may the client send any message other than a single `Identify` before it has received an `Identified`. Doing so will result in the connection being dropped by the server with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum).
|
||||
|
||||
---
|
||||
|
||||
### Creating an authentication string
|
||||
obs-websocket uses SHA256 to transmit authentication credentials. The server starts by sending an object in the `authentication` field of its [`Hello`](#hello). The client processes the authentication challenge and responds via the `authentication` string in [`Identify`](#identify).
|
||||
obs-websocket uses SHA256 to transmit authentication credentials. The server starts by sending an object in the `authentication` key of its `Hello` message data. The client processes the authentication challenge and responds via the `authentication` string in the `Identify` message data.
|
||||
|
||||
For this guide, we'll be using `supersecretpassword` as the password.
|
||||
|
||||
The `authentication` object in [`Hello`](#hello) looks like this (example):
|
||||
The `authentication` object in `Hello` looks like this (example):
|
||||
```json
|
||||
{
|
||||
"challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=",
|
||||
@ -88,17 +88,97 @@ The `authentication` object in [`Hello`](#hello) looks like this (example):
|
||||
|
||||
To generate the authentication string, follow these steps:
|
||||
- Concatenate the websocket password with the `salt` provided by the server (`password + salt`)
|
||||
- Generate an SHA256 binary hash of the result and encode it with base64, known as a base64 secret.
|
||||
- Generate an SHA256 binary hash of the result and base64 encode it, known as a base64 secret.
|
||||
- Concatenate the base64 secret with the `challenge` sent by the server (`base64_secret + challenge`)
|
||||
- Generate a binary SHA256 hash of that result and encode it to base64. You now have your `authentication` string.
|
||||
- Generate a binary SHA256 hash of that result and base64 encode it. You now have your `authentication` string.
|
||||
|
||||
For more info on how to create the `authentication` string, refer to the obs-websocket client libraries listed on the [README](README.md).
|
||||
For real-world examples of the `authentication` string creation, refer to the obs-websocket client libraries listed on the [README](README.md).
|
||||
|
||||
---
|
||||
|
||||
### Enumerations
|
||||
These are the enumeration definitions for various codes used by obs-websocket.
|
||||
|
||||
#### WebSocketOpCode Enum
|
||||
```cpp
|
||||
enum WebSocketOpCode {
|
||||
Hello = 0,
|
||||
Identify = 1,
|
||||
Identified = 2,
|
||||
Reidentify = 3,
|
||||
Event = 5,
|
||||
Request = 6,
|
||||
RequestResponse = 7,
|
||||
RequestBatch = 8,
|
||||
RequestBatchResponse = 9,
|
||||
};
|
||||
```
|
||||
|
||||
#### WebSocketCloseCode Enum
|
||||
```cpp
|
||||
enum WebSocketCloseCode {
|
||||
// Internal only
|
||||
DontClose = 0,
|
||||
// Reserved
|
||||
UnknownReason = 4000,
|
||||
// The requested `Content-Type` specified in the request HTTP header is invalid.
|
||||
InvalidContentType = 4001,
|
||||
// The server was unable to decode the incoming websocket message
|
||||
MessageDecodeError = 4002,
|
||||
// A data key is missing but required
|
||||
MissingDataKey = 4003,
|
||||
// A data key has an invalid type
|
||||
InvalidDataKeyType = 4004,
|
||||
// The specified `op` was invalid or missing
|
||||
UnknownOpCode = 4005,
|
||||
// The client sent a websocket message without first sending `Identify` message
|
||||
NotIdentified = 4006,
|
||||
// The client sent an `Identify` message while already identified
|
||||
AlreadyIdentified = 4007,
|
||||
// The authentication attempt (via `Identify`) failed
|
||||
AuthenticationFailed = 4008,
|
||||
// The server detected the usage of an old version of the obs-websocket protocol.
|
||||
UnsupportedRpcVersion = 4009,
|
||||
// The websocket session has been invalidated by the obs-websocket server.
|
||||
SessionInvalidated = 4010,
|
||||
};
|
||||
```
|
||||
|
||||
#### EventSubscriptions Enum
|
||||
```cpp
|
||||
enum EventSubscription {
|
||||
// Set subscriptions to 0 to disable all events
|
||||
None = 0,
|
||||
// Receive events in the `General` category
|
||||
General = (1 << 0),
|
||||
// Receive events in the `Config` category
|
||||
Config = (1 << 1),
|
||||
// Receive events in the `Scenes` category
|
||||
Scenes = (1 << 2),
|
||||
// Receive events in the `Inputs` category
|
||||
Inputs = (1 << 3),
|
||||
// Receive events in the `Transitions` category
|
||||
Transitions = (1 << 4),
|
||||
// Receive events in the `Filters` category
|
||||
Filters = (1 << 5),
|
||||
// Receive events in the `Outputs` category
|
||||
Outputs = (1 << 6),
|
||||
// Receive events in the `Scene Items` category
|
||||
SceneItems = (1 << 7),
|
||||
// Receive events in the `MediaInputs` category
|
||||
MediaInputs = (1 << 8),
|
||||
// Receive all event categories
|
||||
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs),
|
||||
// InputVolumeMeters event (high-volume)
|
||||
InputVolumeMeters = (1 << 9),
|
||||
// InputActiveStateChanged event (high-volume)
|
||||
InputActiveStateChanged = (1 << 10),
|
||||
// InputShowStateChanged event (high-volume)
|
||||
InputShowStateChanged = (1 << 11),
|
||||
};
|
||||
```
|
||||
Subscriptions are a bitmask system. In many languages, to generate a bitmask that subscribes to `General` and `Scenes`, you would do: `subscriptions = ((1 << 0) | (1 << 2))`
|
||||
|
||||
#### RequestStatus Enum
|
||||
```cpp
|
||||
enum RequestStatus {
|
||||
@ -219,92 +299,26 @@ enum RequestStatus {
|
||||
};
|
||||
```
|
||||
|
||||
#### WebSocketCloseCode Enum
|
||||
```cpp
|
||||
enum WebSocketCloseCode {
|
||||
// Internal only
|
||||
DontClose = 0,
|
||||
// Reserved
|
||||
UnknownReason = 4000,
|
||||
// The server was unable to decode the incoming websocket message
|
||||
MessageDecodeError = 4001,
|
||||
// The specified `messageType` was invalid or missing
|
||||
UnknownMessageType = 4002,
|
||||
// The client sent a websocket message without first sending `Identify` message
|
||||
NotIdentified = 4003,
|
||||
// The client sent an `Identify` message while already identified
|
||||
AlreadyIdentified = 4004,
|
||||
// The authentication attempt (via `Identify`) failed
|
||||
AuthenticationFailed = 4005,
|
||||
// There was an invalid parameter the client's `Identify` message
|
||||
InvalidIdentifyParameter = 4006,
|
||||
// A `Request` or `RequestBatch` was missing its `requestId` or `requestType`
|
||||
RequestMissingRequiredField = 4007,
|
||||
// The websocket session has been invalidated by the obs-websocket server.
|
||||
SessionInvalidated = 4008,
|
||||
// The server detected the usage of an old version of the obs-websocket protocol.
|
||||
UnsupportedProtocolVersion = 4009,
|
||||
// The requested `Content-Type` specified in the request HTTP header is invalid.
|
||||
InvalidContentType = 4010,
|
||||
};
|
||||
```
|
||||
|
||||
#### EventSubscriptions Enum
|
||||
```cpp
|
||||
enum EventSubscription {
|
||||
// Set subscriptions to 0 to disable all events
|
||||
None = 0,
|
||||
// Receive events in the `General` category
|
||||
General = (1 << 0),
|
||||
// Receive events in the `Config` category
|
||||
Config = (1 << 1),
|
||||
// Receive events in the `Scenes` category
|
||||
Scenes = (1 << 2),
|
||||
// Receive events in the `Inputs` category
|
||||
Inputs = (1 << 3),
|
||||
// Receive events in the `Transitions` category
|
||||
Transitions = (1 << 4),
|
||||
// Receive events in the `Filters` category
|
||||
Filters = (1 << 5),
|
||||
// Receive events in the `Outputs` category
|
||||
Outputs = (1 << 6),
|
||||
// Receive events in the `Scene Items` category
|
||||
SceneItems = (1 << 7),
|
||||
// Receive events in the `MediaInputs` category
|
||||
MediaInputs = (1 << 8),
|
||||
// Receive all event categories
|
||||
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs),
|
||||
// InputVolumeMeters event (high-volume)
|
||||
InputVolumeMeters = (1 << 9),
|
||||
// InputActiveStateChanged event (high-volume)
|
||||
InputActiveStateChanged = (1 << 10),
|
||||
// InputShowStateChanged event (high-volume)
|
||||
InputShowStateChanged = (1 << 11),
|
||||
};
|
||||
```
|
||||
Subscriptions are a bitmask system.
|
||||
## Message Types (OpCodes)
|
||||
The following message types are the low-level message types which may be sent to and from obs-websocket.
|
||||
|
||||
In many languages, to generate a bitmask that subscribes to `General` and `Scenes`, you would do: `subscriptions = ((1 << 0) | (1 << 2))`
|
||||
|
||||
|
||||
## Message Types
|
||||
The following message types are the base message types which may be sent to and from obs-websocket.
|
||||
|
||||
**Every** message sent from the obs-websocket server or client must contain these fields, known as the base object:
|
||||
Messages sent from the obs-websocket server or client may contain these first-level fields, known as the base object:
|
||||
```
|
||||
{
|
||||
"messageType": string
|
||||
"op": number,
|
||||
"d": object
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Hello
|
||||
### Hello (OpCode 0)
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Freshly connected websocket client
|
||||
- Description: First message sent from the server immediately on client connection. Contains authentication information if auth is required. Also contains RPC version for version negotiation.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"obsWebSocketVersion": string,
|
||||
@ -318,12 +332,14 @@ The following message types are the base message types which may be sent to and
|
||||
Authentication is required
|
||||
```json
|
||||
{
|
||||
"messageType": "Hello",
|
||||
"obsWebSocketVersion": "5.0.0",
|
||||
"rpcVersion": 1,
|
||||
"authentication": {
|
||||
"challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=",
|
||||
"salt": "lM1GncleQOaCu9lT1yeUZhFYnqhsLLP1G5lAGo3ixaI="
|
||||
"op": 0,
|
||||
"d": {
|
||||
"obsWebSocketVersion": "5.0.0",
|
||||
"rpcVersion": 1,
|
||||
"authentication": {
|
||||
"challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=",
|
||||
"salt": "lM1GncleQOaCu9lT1yeUZhFYnqhsLLP1G5lAGo3ixaI="
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -331,20 +347,22 @@ Authentication is required
|
||||
Authentication is not required
|
||||
```json
|
||||
{
|
||||
"messageType": "Hello",
|
||||
"obsWebSocketVersion": "5.0.0",
|
||||
"rpcVersion": 1
|
||||
"op": 0,
|
||||
"d": {
|
||||
"obsWebSocketVersion": "5.0.0",
|
||||
"rpcVersion": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Identify
|
||||
### Identify (OpCode 1)
|
||||
- Sent from: Freshly connected websocket client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Response to `Hello` message, should contain authentication string if authentication is required, along with PubSub subscriptions and other session parameters.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"rpcVersion": number,
|
||||
@ -362,21 +380,23 @@ Authentication is not required
|
||||
**Example Message:**
|
||||
```json
|
||||
{
|
||||
"messageType": "Identify",
|
||||
"rpcVersion": 1,
|
||||
"authentication": "Dj6cLS+jrNA0HpCArRg0Z/Fc+YHdt2FQfAvgD1mip6Y=",
|
||||
"eventSubscriptions": 33
|
||||
"op": 1,
|
||||
"d": {
|
||||
"rpcVersion": 1,
|
||||
"authentication": "Dj6cLS+jrNA0HpCArRg0Z/Fc+YHdt2FQfAvgD1mip6Y=",
|
||||
"eventSubscriptions": 33
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Identified
|
||||
### Identified (OpCode 2)
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Freshly identified client
|
||||
- Description: The identify request was received and validated, and the connection is now ready for normal operation.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"negotiatedRpcVersion": number
|
||||
@ -387,19 +407,21 @@ Authentication is not required
|
||||
**Example Message:**
|
||||
```json
|
||||
{
|
||||
"messageType": "Identified",
|
||||
"negotiatedRpcVersion": 1
|
||||
"op": 2,
|
||||
"d": {
|
||||
"negotiatedRpcVersion": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reidentify
|
||||
### Reidentify (OpCode 3)
|
||||
- Sent from: Identified client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Sent at any time after initial identification to update the provided session parameters.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"ignoreInvalidMessages": bool(optional) = false,
|
||||
@ -411,12 +433,12 @@ Authentication is not required
|
||||
|
||||
---
|
||||
|
||||
### Event
|
||||
### Event (OpCode 5)
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: All subscribed and identified clients
|
||||
- Description: An event coming from OBS has occured. Eg scene switched, source muted.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"eventType": string,
|
||||
@ -424,14 +446,27 @@ Authentication is not required
|
||||
}
|
||||
```
|
||||
|
||||
**Example Message:**
|
||||
```json
|
||||
{
|
||||
"op": 2,
|
||||
"d": {
|
||||
"eventType": "StudioModeStateChanged",
|
||||
"eventData": {
|
||||
"studioModeEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Request
|
||||
### Request (OpCode 6)
|
||||
- Sent from: Identified client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Client is making a request to obs-websocket. Eg get current scene, create source.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"requestType": string,
|
||||
@ -444,23 +479,25 @@ Authentication is not required
|
||||
**Example Message:**
|
||||
```json
|
||||
{
|
||||
"messageType": "Request",
|
||||
"requestType": "SetCurrentScene",
|
||||
"requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c",
|
||||
"requestData": {
|
||||
"sceneName": "Scene 12"
|
||||
"op": 6,
|
||||
"d": {
|
||||
"requestType": "SetCurrentScene",
|
||||
"requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c",
|
||||
"requestData": {
|
||||
"sceneName": "Scene 12"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### RequestResponse
|
||||
### RequestResponse (OpCode 7)
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Identified client which made the request
|
||||
- Description: obs-websocket is responding to a request coming from a client.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"requestType": string,
|
||||
@ -487,12 +524,14 @@ Authentication is not required
|
||||
Successful Response
|
||||
```json
|
||||
{
|
||||
"messageType": "RequestResponse",
|
||||
"requestType": "SetCurrentScene",
|
||||
"requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c",
|
||||
"requestStatus": {
|
||||
"result": true,
|
||||
"code": 100
|
||||
"op": 7,
|
||||
"d": {
|
||||
"requestType": "SetCurrentScene",
|
||||
"requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c",
|
||||
"requestStatus": {
|
||||
"result": true,
|
||||
"code": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -500,25 +539,27 @@ Successful Response
|
||||
Failure Response
|
||||
```json
|
||||
{
|
||||
"messageType": "RequestResponse",
|
||||
"requestType": "SetCurrentScene",
|
||||
"requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c",
|
||||
"requestStatus": {
|
||||
"result": false,
|
||||
"code": 608,
|
||||
"comment": "Parameter: sceneName"
|
||||
"op": 7,
|
||||
"d": {
|
||||
"requestType": "SetCurrentScene",
|
||||
"requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c",
|
||||
"requestStatus": {
|
||||
"result": false,
|
||||
"code": 608,
|
||||
"comment": "Parameter: sceneName"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### RequestBatch
|
||||
### RequestBatch (OpCode 8)
|
||||
- Sent from: Identified client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Client is making a batch of requests for obs-websocket. Requests are processed serially (in order) by the server.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"requestId": string,
|
||||
@ -527,15 +568,16 @@ Failure Response
|
||||
}
|
||||
```
|
||||
- When `haltOnFailure` is `true`, the processing of requests will be halted on first failure. Returns only the processed requests in [`RequestBatchResponse`](#requestbatchresponse).
|
||||
- Requests in the `requests` array follow the same structure as the `Request` payload data format, however `requestId` is an optional key.
|
||||
|
||||
---
|
||||
|
||||
### RequestBatchResponse
|
||||
### RequestBatchResponse (OpCode 9)
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Identified client which made the request
|
||||
- Description: obs-websocket is responding to a request batch coming from the client.
|
||||
|
||||
**Additional Base Object Fields:**
|
||||
**Data Fields:**
|
||||
```
|
||||
{
|
||||
"requestId": string,
|
||||
|
@ -20,238 +20,226 @@ bool IsSupportedRpcVersion(uint8_t requestedVersion)
|
||||
return false;
|
||||
}
|
||||
|
||||
WebSocketProtocol::ProcessResult SetSessionParameters(SessionPtr session, json incomingMessage)
|
||||
void SetSessionParameters(SessionPtr session, WebSocketProtocol::ProcessResult &ret, json payloadData)
|
||||
{
|
||||
WebSocketProtocol::ProcessResult ret;
|
||||
|
||||
if (incomingMessage.contains("ignoreInvalidMessages")) {
|
||||
if (!incomingMessage["ignoreInvalidMessages"].is_boolean()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidIdentifyParameter;
|
||||
ret.closeReason = "You specified `ignoreInvalidMessages` but the value is not boolean.";
|
||||
return ret;
|
||||
if (payloadData.contains("ignoreInvalidMessages")) {
|
||||
if (!payloadData["ignoreInvalidMessages"].is_boolean()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidDataKeyType;
|
||||
ret.closeReason = "Your `ignoreInvalidMessages` is not a boolean.";
|
||||
return;
|
||||
}
|
||||
session->SetIgnoreInvalidMessages(incomingMessage["ignoreInvalidMessages"]);
|
||||
session->SetIgnoreInvalidMessages(payloadData["ignoreInvalidMessages"]);
|
||||
}
|
||||
|
||||
if (incomingMessage.contains("ignoreNonFatalRequestChecks")) {
|
||||
if (!incomingMessage["ignoreNonFatalRequestChecks"].is_boolean()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidIdentifyParameter;
|
||||
ret.closeReason = "You specified `ignoreNonFatalRequestChecks` but the value is not boolean.";
|
||||
return ret;
|
||||
if (payloadData.contains("ignoreNonFatalRequestChecks")) {
|
||||
if (!payloadData["ignoreNonFatalRequestChecks"].is_boolean()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidDataKeyType;
|
||||
ret.closeReason = "Your `ignoreNonFatalRequestChecks` is not a boolean.";
|
||||
return;
|
||||
}
|
||||
session->SetIgnoreNonFatalRequestChecks(incomingMessage["ignoreNonFatalRequestChecks"]);
|
||||
session->SetIgnoreNonFatalRequestChecks(payloadData["ignoreNonFatalRequestChecks"]);
|
||||
}
|
||||
|
||||
if (incomingMessage.contains("eventSubscriptions")) {
|
||||
if (!incomingMessage["eventSubscriptions"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidIdentifyParameter;
|
||||
ret.closeReason = "You specified `eventSubscriptions` but the value is not an unsigned integer.";
|
||||
return ret;
|
||||
if (payloadData.contains("eventSubscriptions")) {
|
||||
if (!payloadData["eventSubscriptions"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidDataKeyType;
|
||||
ret.closeReason = "Your `eventSubscriptions` is not an unsigned number.";
|
||||
return;
|
||||
}
|
||||
session->SetEventSubscriptions(incomingMessage["eventSubscriptions"]);
|
||||
session->SetEventSubscriptions(payloadData["eventSubscriptions"]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
WebSocketProtocol::ProcessResult WebSocketProtocol::ProcessMessage(SessionPtr session, json incomingMessage)
|
||||
void WebSocketProtocol::ProcessMessage(SessionPtr session, WebSocketProtocol::ProcessResult &ret, uint8_t opCode, json payloadData)
|
||||
{
|
||||
WebSocketProtocol::ProcessResult ret;
|
||||
|
||||
if (!incomingMessage.is_object()) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::MessageDecodeError;
|
||||
ret.closeReason = "You sent a non-object payload.";
|
||||
if (!payloadData.is_object()) {
|
||||
if (payloadData.is_null()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::MissingDataKey;
|
||||
ret.closeReason = "Your payload is missing data (`d`).";
|
||||
} else {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidDataKeyType;
|
||||
ret.closeReason = "Your payload's data (`d`) is not an object.";
|
||||
}
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!incomingMessage.contains("messageType")) {
|
||||
if (incomingMessage.contains("request-type")) {
|
||||
blog(LOG_WARNING, "[WebSocketProtocol::ProcessMessage] Client %s appears to be running a pre-5.0.0 protocol.", session->RemoteAddress().c_str());
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnsupportedProtocolVersion;
|
||||
ret.closeReason = "You appear to be attempting to connect with the pre-5.0.0 plugin protocol. Check to make sure your client is updated.";
|
||||
return ret;
|
||||
}
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnknownMessageType;
|
||||
ret.closeReason = "Your request is missing a `messageType`.";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string messageType = incomingMessage["messageType"];
|
||||
|
||||
if (!session->IsIdentified() && messageType != "Identify") {
|
||||
// Only `Identify` is allowed when not identified
|
||||
if (!session->IsIdentified() && opCode != 1) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::NotIdentified;
|
||||
ret.closeReason = "You attempted to send a non-`Identify` message while not identified.";
|
||||
return ret;
|
||||
ret.closeReason = "You attempted to send a non-Identify message while not identified.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageType == "Request") {
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!incomingMessage.contains("requestId")) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::RequestMissingRequiredField;
|
||||
ret.closeReason = "Your request is missing a `requestId`.";
|
||||
switch (opCode) {
|
||||
case 1: { // Identify
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
if (session->IsIdentified()) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::AlreadyIdentified;
|
||||
ret.closeReason = "You are already Identified with the obs-websocket server.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!incomingMessage["requestType"].is_string()) {
|
||||
incomingMessage["requestType"] = "";
|
||||
}
|
||||
|
||||
RequestHandler requestHandler;
|
||||
Request request(session, incomingMessage["requestType"], incomingMessage["requestData"]);
|
||||
|
||||
RequestResult requestResult = requestHandler.ProcessRequest(request);
|
||||
|
||||
ret.result["messageType"] = "RequestResponse";
|
||||
ret.result["requestType"] = incomingMessage["requestType"];
|
||||
ret.result["requestId"] = incomingMessage["requestId"];
|
||||
ret.result["requestStatus"] = {
|
||||
{"result", requestResult.StatusCode == RequestStatus::Success},
|
||||
{"code", requestResult.StatusCode}
|
||||
};
|
||||
if (!requestResult.Comment.empty())
|
||||
ret.result["requestStatus"]["comment"] = requestResult.Comment;
|
||||
if (requestResult.ResponseData.is_object())
|
||||
ret.result["responseData"] = requestResult.ResponseData;
|
||||
|
||||
return ret;
|
||||
} else if (messageType == "RequestBatch") {
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!incomingMessage.contains("requestId")) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::RequestMissingRequiredField;
|
||||
ret.closeReason = "Your request batch is missing a `requestId`.";
|
||||
if (session->AuthenticationRequired()) {
|
||||
if (!payloadData.contains("authentication")) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::AuthenticationFailed;
|
||||
ret.closeReason = "Your payload's data is missing an `authentication` string, however authentication is required.";
|
||||
return;
|
||||
}
|
||||
if (!Utils::Crypto::CheckAuthenticationString(session->Secret(), session->Challenge(), payloadData["authentication"])) {
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Body")).arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Warning, title, body);
|
||||
}
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::AuthenticationFailed;
|
||||
ret.closeReason = "Authentication failed.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!incomingMessage["requests"].is_array()) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::RequestMissingRequiredField;
|
||||
ret.closeReason = "Your request batch is missing a `requests` or it is not an array.";
|
||||
if (!payloadData.contains("rpcVersion")) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::MissingDataKey;
|
||||
ret.closeReason = "Your payload's data is missing an `rpcVersion`.";
|
||||
return;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto requests = incomingMessage["requests"].get<std::vector<json>>();
|
||||
json results = json::array();
|
||||
if (!payloadData["rpcVersion"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidDataKeyType;
|
||||
ret.closeReason = "Your `rpcVersion` is not an unsigned number.";
|
||||
}
|
||||
|
||||
RequestHandler requestHandler;
|
||||
for (auto requestJson : requests) {
|
||||
if (!requestJson["requestType"].is_string())
|
||||
requestJson["requestType"] = "";
|
||||
uint8_t requestedRpcVersion = payloadData["rpcVersion"];
|
||||
if (!IsSupportedRpcVersion(requestedRpcVersion)) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnsupportedRpcVersion;
|
||||
ret.closeReason = "Your requested RPC version is not supported by this server.";
|
||||
return;
|
||||
}
|
||||
session->SetRpcVersion(requestedRpcVersion);
|
||||
|
||||
Request request(session, requestJson["requestType"], requestJson["requestData"]);
|
||||
SetSessionParameters(session, ret, payloadData);
|
||||
if (ret.closeCode != WebSocketServer::WebSocketCloseCode::DontClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
session->SetIsIdentified(true);
|
||||
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.Identified.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Identified.Body")).arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body);
|
||||
}
|
||||
|
||||
ret.result["op"] = 3;
|
||||
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
} return;
|
||||
case 3: { // Reidentify
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
|
||||
SetSessionParameters(session, ret, payloadData);
|
||||
if (ret.closeCode != WebSocketServer::WebSocketCloseCode::DontClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
ret.result["op"] = 3;
|
||||
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
} return;
|
||||
case 6: { // Request
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!payloadData.contains("requestId")) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::MissingDataKey;
|
||||
ret.closeReason = "Your payload data is missing a `requestId`.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RequestHandler requestHandler;
|
||||
Request request(session, payloadData["requestType"], payloadData["requestData"]);
|
||||
|
||||
RequestResult requestResult = requestHandler.ProcessRequest(request);
|
||||
|
||||
json result;
|
||||
result["requestType"] = requestJson["requestType"];
|
||||
|
||||
if (requestJson.contains("requestId"))
|
||||
result["requestId"] = requestJson["requestId"];
|
||||
|
||||
result["requestStatus"] = {
|
||||
json resultPayloadData;
|
||||
resultPayloadData["requestType"] = payloadData["requestType"];
|
||||
resultPayloadData["requestId"] = payloadData["requestId"];
|
||||
resultPayloadData["requestStatus"] = {
|
||||
{"result", requestResult.StatusCode == RequestStatus::Success},
|
||||
{"code", requestResult.StatusCode}
|
||||
};
|
||||
|
||||
if (!requestResult.Comment.empty())
|
||||
result["requestStatus"]["comment"] = requestResult.Comment;
|
||||
|
||||
resultPayloadData["requestStatus"]["comment"] = requestResult.Comment;
|
||||
if (requestResult.ResponseData.is_object())
|
||||
result["responseData"] = requestResult.ResponseData;
|
||||
|
||||
results.push_back(result);
|
||||
}
|
||||
|
||||
ret.result["messageType"] = "RequestBatchResponse";
|
||||
ret.result["requestId"] = incomingMessage["requestId"];
|
||||
ret.result["results"] = results;
|
||||
|
||||
return ret;
|
||||
} else if (messageType == "Identify") {
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
if (session->IsIdentified()) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::AlreadyIdentified;
|
||||
ret.closeReason = "You are already Identified with the obs-websocket server.";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (session->AuthenticationRequired()) {
|
||||
if (!incomingMessage.contains("authentication")) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidIdentifyParameter;
|
||||
ret.closeReason = "Your `Identify` payload is missing an `authentication` string, however authentication is required.";
|
||||
return ret;
|
||||
}
|
||||
if (!Utils::Crypto::CheckAuthenticationString(session->Secret(), session->Challenge(), incomingMessage["authentication"])) {
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Body")).arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Warning, title, body);
|
||||
resultPayloadData["responseData"] = requestResult.ResponseData;
|
||||
ret.result["op"] = 7;
|
||||
ret.result["d"] = resultPayloadData;
|
||||
} return;
|
||||
case 8: { // RequestBatch
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!payloadData.contains("requestId")) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::MissingDataKey;
|
||||
ret.closeReason = "Your payload data is missing a `requestId`.";
|
||||
}
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::AuthenticationFailed;
|
||||
ret.closeReason = "Authentication failed.";
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!incomingMessage.contains("rpcVersion") || !incomingMessage["rpcVersion"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidIdentifyParameter;
|
||||
ret.closeReason = "Your Identify is missing `rpcVersion` or is not an integer.";
|
||||
return ret;
|
||||
}
|
||||
uint8_t requestedRpcVersion = incomingMessage["rpcVersion"];
|
||||
if (!IsSupportedRpcVersion(requestedRpcVersion)) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnsupportedProtocolVersion;
|
||||
ret.closeReason = "Your requested RPC version is not supported by this server.";
|
||||
return ret;
|
||||
}
|
||||
session->SetRpcVersion(requestedRpcVersion);
|
||||
if (!payloadData.contains("requests")) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::MissingDataKey;
|
||||
ret.closeReason = "Your payload data is missing a `requests`.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
WebSocketProtocol::ProcessResult parameterResult = SetSessionParameters(session, incomingMessage);
|
||||
if (ret.closeCode != WebSocketServer::WebSocketCloseCode::DontClose) {
|
||||
return parameterResult;
|
||||
}
|
||||
if (!payloadData["requests"].is_array()) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidDataKeyType;
|
||||
ret.closeReason = "Your `requests` is not an array.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
session->SetIsIdentified(true);
|
||||
std::vector<json> requests = payloadData["requests"];
|
||||
json results = json::array();
|
||||
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.Identified.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Identified.Body")).arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body);
|
||||
}
|
||||
RequestHandler requestHandler;
|
||||
for (auto requestJson : requests) {
|
||||
Request request(session, requestJson["requestType"], requestJson["requestData"]);
|
||||
|
||||
ret.result["messageType"] = "Identified";
|
||||
ret.result["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
return ret;
|
||||
} else if (messageType == "Reidentify") {
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
RequestResult requestResult = requestHandler.ProcessRequest(request);
|
||||
|
||||
WebSocketProtocol::ProcessResult parameterResult = SetSessionParameters(session, incomingMessage);
|
||||
if (ret.closeCode != WebSocketServer::WebSocketCloseCode::DontClose) {
|
||||
return parameterResult;
|
||||
}
|
||||
json result;
|
||||
result["requestType"] = requestJson["requestType"];
|
||||
|
||||
ret.result["messageType"] = "Identified";
|
||||
ret.result["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
return ret;
|
||||
} else {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnknownMessageType;
|
||||
ret.closeReason = std::string("Unknown message type: %s") + messageType;
|
||||
}
|
||||
return ret;
|
||||
if (requestJson.contains("requestId"))
|
||||
result["requestId"] = requestJson["requestId"];
|
||||
|
||||
result["requestStatus"] = {
|
||||
{"result", requestResult.StatusCode == RequestStatus::Success},
|
||||
{"code", requestResult.StatusCode}
|
||||
};
|
||||
|
||||
if (!requestResult.Comment.empty())
|
||||
result["requestStatus"]["comment"] = requestResult.Comment;
|
||||
|
||||
if (requestResult.ResponseData.is_object())
|
||||
result["responseData"] = requestResult.ResponseData;
|
||||
|
||||
results.push_back(result);
|
||||
}
|
||||
|
||||
ret.result["op"] = 9;
|
||||
ret.result["d"]["requestId"] = payloadData["requestId"];
|
||||
ret.result["d"]["results"] = results;
|
||||
} return;
|
||||
default:
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnknownOpCode;
|
||||
ret.closeReason = std::string("Unknown OpCode: %s") + std::to_string(opCode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -19,5 +19,5 @@ namespace WebSocketProtocol {
|
||||
json result;
|
||||
};
|
||||
|
||||
ProcessResult ProcessMessage(SessionPtr session, json incomingMessage);
|
||||
void ProcessMessage(SessionPtr session, ProcessResult &ret, uint8_t opCode, json incomingMessage);
|
||||
}
|
||||
|
@ -197,10 +197,10 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, std::string eventT
|
||||
QtConcurrent::run(&_threadPool, [=]() {
|
||||
// Populate message object
|
||||
json eventMessage;
|
||||
eventMessage["messageType"] = "Event";
|
||||
eventMessage["eventType"] = eventType;
|
||||
eventMessage["op"] = 5;
|
||||
eventMessage["d"]["eventType"] = eventType;
|
||||
if (eventData.is_object())
|
||||
eventMessage["eventData"] = eventData;
|
||||
eventMessage["d"]["eventData"] = eventData;
|
||||
|
||||
// Initialize objects. The broadcast process only dumps the data when its needed.
|
||||
std::string messageJson;
|
||||
@ -271,18 +271,20 @@ void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
|
||||
}
|
||||
|
||||
// Build `Hello`
|
||||
json helloMessage;
|
||||
helloMessage["messageType"] = "Hello";
|
||||
helloMessage["obsWebSocketVersion"] = OBS_WEBSOCKET_VERSION;
|
||||
helloMessage["rpcVersion"] = OBS_WEBSOCKET_RPC_VERSION;
|
||||
json helloMessageData;
|
||||
helloMessageData["obsWebSocketVersion"] = OBS_WEBSOCKET_VERSION;
|
||||
helloMessageData["rpcVersion"] = OBS_WEBSOCKET_RPC_VERSION;
|
||||
if (AuthenticationRequired) {
|
||||
session->SetSecret(AuthenticationSecret);
|
||||
std::string sessionChallenge = Utils::Crypto::GenerateSalt();
|
||||
session->SetChallenge(sessionChallenge);
|
||||
helloMessage["authentication"] = {};
|
||||
helloMessage["authentication"]["challenge"] = sessionChallenge;
|
||||
helloMessage["authentication"]["salt"] = AuthenticationSalt;
|
||||
helloMessageData["authentication"] = json::object();
|
||||
helloMessageData["authentication"]["challenge"] = sessionChallenge;
|
||||
helloMessageData["authentication"]["salt"] = AuthenticationSalt;
|
||||
}
|
||||
json helloMessage;
|
||||
helloMessage["op"] = 0;
|
||||
helloMessage["d"] = helloMessageData;
|
||||
|
||||
sessionLock.unlock();
|
||||
|
||||
@ -412,8 +414,39 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
|
||||
if (_debugEnabled)
|
||||
blog(LOG_INFO, "[WebSocketServer::onMessage] Incoming message (decoded):\n%s", incomingMessage.dump(2).c_str());
|
||||
|
||||
WebSocketProtocol::ProcessResult ret = WebSocketProtocol::ProcessMessage(session, incomingMessage);
|
||||
WebSocketProtocol::ProcessResult ret;
|
||||
|
||||
// Verify incoming message is an object
|
||||
if (!incomingMessage.is_object()) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::MessageDecodeError;
|
||||
ret.closeReason = "You sent a non-object payload.";
|
||||
goto skipProcessing;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Disconnect client if 4.x protocol is detected
|
||||
if (!session->IsIdentified() && incomingMessage.contains("request-type")) {
|
||||
blog(LOG_WARNING, "[WebSocketProtocol::ProcessMessage] Client %s appears to be running a pre-5.0.0 protocol.", session->RemoteAddress().c_str());
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnsupportedRpcVersion;
|
||||
ret.closeReason = "You appear to be attempting to connect with the pre-5.0.0 plugin protocol. Check to make sure your client is updated.";
|
||||
goto skipProcessing;
|
||||
}
|
||||
|
||||
// Validate op code
|
||||
if (!incomingMessage.contains("op")) {
|
||||
if (!session->IgnoreInvalidMessages()) {
|
||||
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnknownOpCode;
|
||||
ret.closeReason = std::string("Your request is missing an `op`.");
|
||||
goto skipProcessing;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
WebSocketProtocol::ProcessMessage(session, ret, incomingMessage["op"], incomingMessage["d"]);
|
||||
|
||||
skipProcessing:
|
||||
if (ret.closeCode != WebSocketCloseCode::DontClose) {
|
||||
websocketpp::lib::error_code errorCode;
|
||||
_server.close(hdl, ret.closeCode, ret.closeReason, errorCode);
|
||||
|
@ -15,33 +15,6 @@ class WebSocketServer : QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum WebSocketCloseCode {
|
||||
// Internal only
|
||||
DontClose = 0,
|
||||
// Reserved
|
||||
UnknownReason = 4000,
|
||||
// The server was unable to decode the incoming websocket message
|
||||
MessageDecodeError = 4001,
|
||||
// The specified `messageType` was invalid or missing
|
||||
UnknownMessageType = 4002,
|
||||
// The client sent a websocket message without first sending `Identify` message
|
||||
NotIdentified = 4003,
|
||||
// The client sent an `Identify` message while already identified
|
||||
AlreadyIdentified = 4004,
|
||||
// The authentication attempt (via `Identify`) failed
|
||||
AuthenticationFailed = 4005,
|
||||
// There was an invalid parameter the client's `Identify` message
|
||||
InvalidIdentifyParameter = 4006,
|
||||
// A `Request` or `RequestBatch` was missing its `requestId` or `requestType`
|
||||
RequestMissingRequiredField = 4007,
|
||||
// The websocket session has been invalidated by the obs-websocket server.
|
||||
SessionInvalidated = 4008,
|
||||
// The server detected the usage of an old version of the obs-websocket protocol.
|
||||
UnsupportedProtocolVersion = 4009,
|
||||
// The requested `Content-Type` specified in the request HTTP header is invalid.
|
||||
InvalidContentType = 4010,
|
||||
};
|
||||
|
||||
enum WebSocketEncoding {
|
||||
Json,
|
||||
MsgPack
|
||||
@ -56,6 +29,45 @@ class WebSocketServer : QObject
|
||||
bool isIdentified;
|
||||
};
|
||||
|
||||
enum WebSocketOpCode {
|
||||
Hello = 0,
|
||||
Identify = 1,
|
||||
Identified = 2,
|
||||
Reidentify = 3,
|
||||
Event = 5,
|
||||
Request = 6,
|
||||
RequestResponse = 7,
|
||||
RequestBatch = 8,
|
||||
RequestBatchResponse = 9,
|
||||
};
|
||||
|
||||
enum WebSocketCloseCode {
|
||||
// Internal only
|
||||
DontClose = 0,
|
||||
// Reserved
|
||||
UnknownReason = 4000,
|
||||
// The requested `Content-Type` specified in the request HTTP header is invalid.
|
||||
InvalidContentType = 4001,
|
||||
// The server was unable to decode the incoming websocket message
|
||||
MessageDecodeError = 4002,
|
||||
// A data key is missing but required
|
||||
MissingDataKey = 4003,
|
||||
// A data key has an invalid type
|
||||
InvalidDataKeyType = 4004,
|
||||
// The specified `op` was invalid or missing
|
||||
UnknownOpCode = 4005,
|
||||
// The client sent a websocket message without first sending `Identify` message
|
||||
NotIdentified = 4006,
|
||||
// The client sent an `Identify` message while already identified
|
||||
AlreadyIdentified = 4007,
|
||||
// The authentication attempt (via `Identify`) failed
|
||||
AuthenticationFailed = 4008,
|
||||
// The server detected the usage of an old version of the obs-websocket RPC protocol.
|
||||
UnsupportedRpcVersion = 4009,
|
||||
// The websocket session has been invalidated by the obs-websocket server.
|
||||
SessionInvalidated = 4010,
|
||||
};
|
||||
|
||||
WebSocketServer();
|
||||
~WebSocketServer();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user