Expose OBS controls to browser sources #281

Merged
sebastian-s-beckmann merged 1 commit from gxalpha-expose-frontend-api into master 2021-08-23 09:47:06 -05:00
sebastian-s-beckmann commented 2021-04-03 05:47:08 -05:00 (Migrated from github.com)

Description

Exposes more functions of the Frontend API to the webpage.
Adds a button to the source properties that lets the user decide whether the webpage should be allowed to access it.

Change to UI:
image

Motivation and Context

I feel like it might be useful for some people to provide some scripting support for JS (especially since the current functionality allows to save the replay buffer but nothing more, which appeared odd to me.)

The button is there to stop webpages from abusing this functionality, I also added a function for the webpage to know whether it is allowed to access the OBS controls. The button is defaulted to false (for obvious reasons).

I also put the current saveReplayBuffer() function behind the permission-button for consistency reasons. This would be a potentially breaking change to existing functionality. I see that this change could be debatable.

How Has This Been Tested?

Successfully built and run on macOS 11.2.3.
Tested all the functions of the buttons in the screenshot above.
Tested functionality of the "Grant webpage control over OBS"-button.

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change) (Default behavior is Read-Only, while before the replay buffer could always be saved)

Checklist:

  • My code has been run through clang-format.
  • I have read the contributing document.
  • My code is not on the master branch.
  • The code has been tested.
  • All commit messages are properly formatted and commits squashed where appropriate.
  • I have included updates to all appropriate documentation.

Additional information

These are obviously not all of the frontend-functions which in the long run I'd kind of like to add.
That would give easier access to scripts that other people (and external providers) might make and publish, since you'd just have to add a browser source with the JS script, instead of downloading and pasting it somewhere while having to update it when new functionality is added to the script.
Feedback regarding this idea is appreciated.

Also congrats for reaching the RC-phase of v27 :D

<!--- Please fill out the following template, which will help other contributors review your Pull Request. --> <!--- Make sure you’ve read the contribution guidelines here: https://github.com/obsproject/obs-studio/blob/master/CONTRIBUTING.rst --> ### Description <!--- Describe your changes in detail. --> <!--- If this change includes UI elements, please include screenshots. --> Exposes more functions of the Frontend API to the webpage. Adds a button to the source properties that lets the user decide whether the webpage should be allowed to access it. Change to UI: <img width="720" alt="image" src="https://user-images.githubusercontent.com/59806498/113475958-a038de80-9478-11eb-8eff-ec4233c105cc.png"> ### Motivation and Context <!--- Why is this change required? What problem does it solve? --> <!--- If it fixes an open GitHub Issue, or implements feature request --> <!--- from the Ideas page, please link to the issue here. --> I feel like it might be useful for some people to provide some scripting support for JS (especially since the current functionality allows to save the replay buffer but nothing more, which appeared odd to me.) The button is there to stop webpages from abusing this functionality, I also added a function for the webpage to know whether it is allowed to access the OBS controls. The button is defaulted to false (for obvious reasons). I also put the current saveReplayBuffer() function behind the permission-button for consistency reasons. This would be a potentially breaking change to existing functionality. I see that this change could be debatable. ### How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment (hardware, OS version, etc.),--> <!--- and the tests you ran, including how it may affect other areas of code. --> Successfully built and run on macOS 11.2.3. Tested all the functions of the buttons in the screenshot above. Tested functionality of the "Grant webpage control over OBS"-button. ### Types of changes <!--- What types of changes does your PR introduce? Uncomment all that apply --> <!--- - Bug fix (non-breaking change which fixes an issue) --> - New feature (non-breaking change which adds functionality) <!--- - Performance enhancement (non-breaking change which improves efficiency) --> <!--- - Code cleanup (non-breaking change which makes code smaller or more readable) --> - Breaking change (fix or feature that would cause existing functionality to change) (Default behavior is Read-Only, while before the replay buffer could always be saved) <!--- - Documentation (a change to documentation pages) --> ### Checklist: <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [x] My code has been run through [clang-format](https://github.com/obsproject/obs-studio/blob/master/.clang-format). - [x] I have read the [**contributing** document](https://github.com/obsproject/obs-studio/blob/master/CONTRIBUTING.rst). - [x] My code is not on the master branch. - [x] The code has been tested. - [x] All commit messages are properly formatted and commits squashed where appropriate. - [x] I have included updates to all appropriate documentation. ### Additional information These are obviously not all of the frontend-functions which in the long run I'd kind of like to add. That would give easier access to scripts that other people (and external providers) might make and publish, since you'd just have to add a browser source with the JS script, instead of downloading and pasting it somewhere while having to update it when new functionality is added to the script. Feedback regarding this idea is appreciated. Also congrats for reaching the RC-phase of v27 :D
RytoEX commented 2021-04-03 10:46:47 -05:00 (Migrated from github.com)

Commits to obs-browser don't need to be prefixed by obs-browser.

Commits to obs-browser don't need to be prefixed by `obs-browser`.
sebastian-s-beckmann commented 2021-04-03 14:55:15 -05:00 (Migrated from github.com)

Oh yeah makes sense, I'll remove it
Probably not today anymore though

Oh yeah makes sense, I'll remove it Probably not today anymore though
WizardCM (Migrated from github.com) reviewed 2021-05-11 18:47:12 -05:00
WizardCM (Migrated from github.com) left a comment

I'll review the capabilities of this properly after v27 is out, but thank you so much for doing this PR. I've been meaning to do it forever.

I'll review the capabilities of this properly after v27 is out, but thank you so much for doing this PR. I've been meaning to do it forever.
@ -124,3 +129,2 @@
for (std::string name : functions) {
for (std::string name : exposedFunctions) {
CefRefPtr<CefV8Value> func =
WizardCM (Migrated from github.com) commented 2021-05-11 18:45:35 -05:00

As a side note, before I review/merge this PR, I'll first be sure to submit my commit to simplify this code, which'll help this implementation significantly.

It'll become something like this instead:

	std::vector<std::string> functions = {"getCurrentScene", "getStatus", "saveReplayBuffer"};

	for (std::string name : functions) {
		CefRefPtr<CefV8Value> func =
			CefV8Value::CreateFunction(name, this);
		obsStudioObj->SetValue(name, func, V8_PROPERTY_ATTRIBUTE_NONE);
	}
As a side note, before I review/merge this PR, I'll first be sure to submit my commit to simplify this code, which'll help this implementation significantly. It'll become something like this instead: ```cpp std::vector<std::string> functions = {"getCurrentScene", "getStatus", "saveReplayBuffer"}; for (std::string name : functions) { CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction(name, this); obsStudioObj->SetValue(name, func, V8_PROPERTY_ATTRIBUTE_NONE); }
WizardCM commented 2021-06-15 03:40:25 -05:00 (Migrated from github.com)

Please adapt your PR for the following change: https://github.com/obsproject/obs-browser/commit/450ff0

Additionally, one thing I debated when writing a similar capability is the idea of "access levels" - for example, starting the replay buffer is much safer than starting streaming. A simple dropdown of "Allow read-only access", "Allow basic control (start/stop replay buffer, etc)" and "Allow full control (start/stop streaming without warning, etc)."

Please adapt your PR for the following change: https://github.com/obsproject/obs-browser/commit/450ff0 Additionally, one thing I debated when writing a similar capability is the idea of "access levels" - for example, starting the replay buffer is much safer than starting streaming. A simple dropdown of "Allow read-only access", "Allow basic control (start/stop replay buffer, etc)" and "Allow full control (start/stop streaming without warning, etc)."
sebastian-s-beckmann commented 2021-06-15 04:41:24 -05:00 (Migrated from github.com)

Alright, will do

Alright, will do
sebastian-s-beckmann commented 2021-06-15 13:57:49 -05:00 (Migrated from github.com)

Argh, Github closes PRs if you remove the commit you want to merge (temporarily)?
I hope it will automatically reopen it when I put it back in, otherwise I'll open a new one

Argh, Github closes PRs if you remove the commit you want to merge (temporarily)? I hope it will automatically reopen it when I put it back in, otherwise I'll open a new one
sebastian-s-beckmann commented 2021-06-16 13:55:47 -05:00 (Migrated from github.com)

Alright, here we go.
UI now looks like this (the missing space behind the commas is a macOS-OBS bug and OBS-wide, not related to the en-US.ini):
image
Default is Read-Only.

There are two additional things I noticed while testing:

  • If no permission is given for a given function, it will silently fail. I personally think thats OK, since I would expect scripts to check the permissions beforehand
  • The event callbacks should probably also be behind the permission-wall, but I'm not sure if that's in scope for this PR
Alright, here we go. UI now looks like this (the missing space behind the commas is a macOS-OBS bug and OBS-wide, not related to the en-US.ini): <img width="672" alt="image" src="https://user-images.githubusercontent.com/59806498/122275960-93a71c80-cee4-11eb-8891-8e2d82aa7825.png"> Default is Read-Only. There are two additional things I noticed while testing: - If no permission is given for a given function, it will silently fail. I personally think thats OK, since I would expect scripts to check the permissions beforehand - The event callbacks should probably also be behind the permission-wall, but I'm not sure if that's in scope for this PR
WizardCM (Migrated from github.com) reviewed 2021-06-16 19:12:55 -05:00
@ -11,6 +11,12 @@ RestartCEF="Restart CEF"
BrowserSource="Browser"
WizardCM (Migrated from github.com) commented 2021-06-16 18:56:21 -05:00

This text does not match the actual permissions you've provided - basic only provides "Save Replay Buffer". Maybe Basic should remain as-is, and then Start/Stop replay buffer & VirtualCam etc without Start/Stop Streaming/Recording should be "Advanced"? I'm not sure.

This text does not match the actual permissions you've provided - basic only provides "Save Replay Buffer". Maybe Basic should remain as-is, and then Start/Stop replay buffer & VirtualCam etc without Start/Stop Streaming/Recording should be "Advanced"? I'm not sure.
sebastian-s-beckmann (Migrated from github.com) reviewed 2021-06-17 00:03:25 -05:00
@ -11,6 +11,12 @@ RestartCEF="Restart CEF"
BrowserSource="Browser"
sebastian-s-beckmann (Migrated from github.com) commented 2021-06-17 00:03:25 -05:00

Oh, you're right - I meant "Save replay buffer" when it says "Start/Stop", will fix

I'm personally not sure it would be a good idea to give the user even more possibilities, though I have no strong feelings about that

Oh, you're right - I meant "Save replay buffer" when it says "Start/Stop", will fix I'm personally not sure it would be a good idea to give the user even more possibilities, though I have no strong feelings about that
WizardCM (Migrated from github.com) reviewed 2021-06-17 00:19:23 -05:00
@ -11,6 +11,12 @@ RestartCEF="Restart CEF"
BrowserSource="Browser"
WizardCM (Migrated from github.com) commented 2021-06-17 00:19:22 -05:00

Yep, I have no strong feelings either way myself.

Yep, I have no strong feelings either way myself.
sebastian-s-beckmann (Migrated from github.com) reviewed 2021-06-17 03:54:38 -05:00
@ -11,6 +11,12 @@ RestartCEF="Restart CEF"
BrowserSource="Browser"
sebastian-s-beckmann (Migrated from github.com) commented 2021-06-17 03:54:38 -05:00

Fixed the wording on the current behaviour, left the levels how they are for now.

Maybe someone else has opinions on the above ^

Fixed the wording on the current behaviour, left the levels how they are for now. Maybe someone else has opinions on the above ^
sebastian-s-beckmann (Migrated from github.com) reviewed 2021-06-18 01:05:02 -05:00
@ -11,6 +11,12 @@ RestartCEF="Restart CEF"
BrowserSource="Browser"
sebastian-s-beckmann (Migrated from github.com) commented 2021-06-18 01:05:02 -05:00

Update:
Thought about this, an "Advanced" level probably is a good idea.
That could be used as well for say switching scenes (which is something I plan on adding once the initial functionality is merged), and only the most dangerous controls (aka start/stop of the outputs) would be under "all".

The levels would then be:

  • None
  • Read-Only
  • Basic (functions that shouldn't be able to break stuff)
  • Advanced (functions that are able to break the current setup in non-fatal ways)
  • All (functions that are able to break everything)

I still think 5 options might be a little much but probably a drawback worth taking.

If you agree, I'd add this.

Update: Thought about this, an "Advanced" level probably is a good idea. That could be used as well for say switching scenes (which is something I plan on adding once the initial functionality is merged), and only the most dangerous controls (aka start/stop of the outputs) would be under "all". The levels would then be: - None - Read-Only - Basic (functions that shouldn't be able to break stuff) - Advanced (functions that are able to break the current setup in non-fatal ways) - All (functions that are able to break everything) I still think 5 options might be a little much but probably a drawback worth taking. If you agree, I'd add this.
WizardCM (Migrated from github.com) reviewed 2021-06-18 01:14:09 -05:00
@ -11,6 +11,12 @@ RestartCEF="Restart CEF"
BrowserSource="Browser"
WizardCM (Migrated from github.com) commented 2021-06-18 01:14:09 -05:00

I would say that's a worthwhile distinction.

I would say that's a worthwhile distinction.
sebastian-s-beckmann commented 2021-06-18 08:52:38 -05:00 (Migrated from github.com)

Added "Advanced" as permission as discussed with WizardCM, also added the required permissions to the functions in the README.

Added "Advanced" as permission as discussed with WizardCM, also added the required permissions to the functions in the README.
RytoEX (Migrated from github.com) requested changes 2021-07-11 04:49:05 -05:00
RytoEX (Migrated from github.com) left a comment

Is there a reason why getControlLevel is callable with the None permission level instead of the ReadOnly level?

I'd like it if the commit message could be more descriptive. Particularly, it would be nice if the commit subject were a bit clearer.

I'm not fond of the huge switch statement in browser-client.cpp, but I suppose it can be cleaned up at a later date.

Is there a reason why getControlLevel is callable with the None permission level instead of the ReadOnly level? I'd like it if the commit message could be more descriptive. Particularly, it would be nice if the commit subject were a bit clearer. I'm not fond of the huge switch statement in browser-client.cpp, but I suppose it can be cleaned up at a later date.
@ -60,8 +60,24 @@ Descriptions for these events can be [found here](https://obsproject.com/docs/re
* obsVirtualcamStopped
RytoEX (Migrated from github.com) commented 2021-07-10 01:48:55 -05:00

Nitpick: level

The doc item should match the case of the actual parameter.

Nitpick: level The doc item should match the case of the actual parameter.
@ -65,0 +77,4 @@
```
#### Get the current scene
Permissions required: READ_ONLY
RytoEX (Migrated from github.com) commented 2021-07-10 01:56:58 -05:00

Insert a blank line between the end of the last code block and the next header for readability.

Insert a blank line between the end of the last code block and the next header for readability.
@ -350,2 +354,4 @@
}
bool IsValidFunction(std::string function)
{
RytoEX (Migrated from github.com) commented 2021-07-11 04:46:10 -05:00

I did some simplistic timing and it seems that an IsValidFunction(name) function that uses a vector or an array with std::find generally performs as good or better than this approach. This approach only performs better if the sought function name is in one of the first few conditions checked (i.e., up to startRecording). The savings are minute, but it looks nicer.

If you don't want to change this in this PR, I don't consider it a blocker, and I can address it later.

I did some simplistic timing and it seems that an `IsValidFunction(name)` function that uses a vector or an array with std::find generally performs as good or better than this approach. This approach only performs better if the sought function name is in one of the first few conditions checked (i.e., up to startRecording). The savings are minute, but it looks nicer. If you don't want to change this in this PR, I don't consider it a blocker, and I can address it later.
@ -205,6 +207,30 @@ static obs_properties_t *browser_source_get_properties(void *data)
obs_properties_add_bool(props, "restart_when_active",
RytoEX (Migrated from github.com) commented 2021-07-11 02:18:21 -05:00

It'd be simpler and a lot less code to just call obs_property_list_add_int for each enum entry. I don't think the default case in the switch gains us anything here.

obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.None"), LEVEL_NONE);
obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.ReadOnly"), LEVEL_READ_ONLY);
obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.Basic"), LEVEL_BASIC);
obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.Advanced"), LEVEL_ADVANCED);
obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.All"), LEVEL_ALL);
It'd be simpler and a lot less code to just call `obs_property_list_add_int` for each enum entry. I don't think the default case in the switch gains us anything here. ``` obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.None"), LEVEL_NONE); obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.ReadOnly"), LEVEL_READ_ONLY); obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.Basic"), LEVEL_BASIC); obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.Advanced"), LEVEL_ADVANCED); obs_property_list_add_int(controlLevel, obs_module_text("WebpageControlLevel.Level.All"), LEVEL_ALL); ```
sebastian-s-beckmann commented 2021-07-11 06:35:18 -05:00 (Migrated from github.com)

Is there a reason why getControlLevel is callable with the None permission level instead of the ReadOnly level?

Yes. getControlLevel tells the webpage how many permissions it has, so that it knows whether a method is callable or not. Locking it behind ReadOnly would kind a vicious circle - If the permissions would be set to none, the page can't try to find out what level it is, and if it doesn't know which level it is, it doesn't know if it can call that function.
Also, putting the function behind ReadOnly won't actually hide anything - say the user sets the level to none and the function gets called, it would error out, basically telling the page it is set to none.
(I hope this answers your question)

I'll address the other issues.

> Is there a reason why getControlLevel is callable with the None permission level instead of the ReadOnly level? Yes. getControlLevel tells the webpage how many permissions it has, so that it knows whether a method is callable or not. Locking it behind ReadOnly would kind a vicious circle - If the permissions would be set to none, the page can't try to find out what level it is, and if it doesn't know which level it is, it doesn't know if it can call that function. Also, putting the function behind ReadOnly won't actually hide anything - say the user sets the level to none and the function gets called, it would error out, basically telling the page it is set to none. (I hope this answers your question) I'll address the other issues.
sebastian-s-beckmann (Migrated from github.com) reviewed 2021-07-12 15:48:11 -05:00
@ -205,6 +207,30 @@ static obs_properties_t *browser_source_get_properties(void *data)
obs_properties_add_bool(props, "restart_when_active",
sebastian-s-beckmann (Migrated from github.com) commented 2021-07-12 15:48:11 -05:00

Done.

Now stupidly, clang-format decided to split each of these lines into four lines, removing most readability benefits, so I decided to turn it off for that bit and try to keep the lines at a reasonable length by formatting myself - I hope that ok

Done. Now stupidly, clang-format decided to split each of these lines into four lines, removing most readability benefits, so I decided to turn it off for that bit and try to keep the lines at a reasonable length by formatting myself - I hope that ok
RytoEX (Migrated from github.com) reviewed 2021-07-29 02:27:45 -05:00
@ -205,6 +207,30 @@ static obs_properties_t *browser_source_get_properties(void *data)
obs_properties_add_bool(props, "restart_when_active",
RytoEX (Migrated from github.com) commented 2021-07-29 02:27:45 -05:00

Readability wasn't my only concern. The change also vastly simplifies the code (removed a for loop and switch statement that was in the loop). Personally, I don't think we need to care about switching clang-format off and on. If clang-format makes each function call four lines long, so be it. That said, I'm also not hung up on it, I just don't see the point. @WizardCM ?

Readability wasn't my only concern. The change also vastly simplifies the code (removed a for loop and switch statement that was in the loop). Personally, I don't think we need to care about switching clang-format off and on. If clang-format makes each function call four lines long, so be it. That said, I'm also not hung up on it, I just don't see the point. @WizardCM ?
sebastian-s-beckmann (Migrated from github.com) reviewed 2021-08-17 17:14:44 -05:00
@ -350,2 +354,4 @@
}
bool IsValidFunction(std::string function)
{
sebastian-s-beckmann (Migrated from github.com) commented 2021-08-17 17:14:43 -05:00

Done

Done
notr1ch (Migrated from github.com) reviewed 2021-08-22 19:42:09 -05:00
@ -11,6 +11,12 @@ RestartCEF="Restart CEF"
BrowserSource="Browser"
notr1ch (Migrated from github.com) commented 2021-08-22 19:42:09 -05:00

I'd probably change this to "Page permissions" or "OBS API permissions" (for some reason "webpage" feels off to me in this context) with the additional context that this is access to OBS, eg:

None: No access to OBS
ReadOnly: Read-only access to OBS (current scene and status)
Basic: Basic access to OBS (save replay buffer)
Advanced: Advanced access to OBS (change scenes, start/stop replay buffer)
Full: Full access to OBS (start/stop streaming, recording, etc).

Otherwise I feel "access" is a bit vague as to what exactly it's providing access to, and "None" doesn't fit the style of the other options ("No access" would be better IMO). I feel it's important to get the wording right as this is a potentially very dangerous feature if users don't understand it properly and grant random websites full access.

I'd probably change this to "Page permissions" or "OBS API permissions" (for some reason "webpage" feels off to me in this context) with the additional context that this is access to OBS, eg: None: No access to OBS ReadOnly: Read-only access to OBS (current scene and status) Basic: Basic access to OBS (save replay buffer) Advanced: Advanced access to OBS (change scenes, start/stop replay buffer) Full: Full access to OBS (start/stop streaming, recording, etc). Otherwise I feel "access" is a bit vague as to what exactly it's providing access to, and "None" doesn't fit the style of the other options ("No access" would be better IMO). I feel it's important to get the wording right as this is a potentially very dangerous feature if users don't understand it properly and grant random websites full access.
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-obsproject/obs-browser!281
No description provided.