Add WebRTC Camera Streaming Support and Related Documentation - #24
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly expands the Prusa Connect client's capabilities by integrating low-latency WebRTC camera streaming. It provides both a command-line interface for users to easily view live camera feeds from their 3D printers and SDK examples for developers to integrate this functionality into their applications. The changes also improve the project's documentation quality and build process by adopting new documentation tools and standards. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant new functionality for WebRTC camera streaming, both in the CLI and as an SDK example. However, the camera webrtc CLI command has critical security vulnerabilities, specifically Cross-Site Scripting (XSS) and Path Traversal, due to injecting unvalidated user input into the HTML template and using it for temporary file paths. These could lead to arbitrary script execution or unintended file writes. Beyond these security concerns, the implementation could be improved for robustness and maintainability by addressing the use of private SDK members, removing debug code, making development-mode file path resolution less fragile, fixing a potential race condition in the WebRTC JavaScript template, and refactoring a long function.
| # Escape backticks for javascript template literal | ||
| proto_content = proto_content.replace("`", "\\`") | ||
|
|
||
| html_content = html_content.replace("{{ CAMERA_TOKEN }}", token) |
There was a problem hiding this comment.
The camera webrtc command is vulnerable to Cross-Site Scripting (XSS). The user-provided camera_id (which becomes the token variable) is injected directly into a JavaScript string literal in the generated HTML file using simple string replacement. An attacker could provide a malicious camera_id such as "; alert('XSS'); // which would result in the following JavaScript being executed when the user opens the stream: const CAMERA_TOKEN = ""; alert('XSS'); //";. Since the user's JWT token is also embedded in this same file, an attacker could use this XSS to exfiltrate the session token.
Remediation: Sanitize or escape the token before injecting it into the HTML template. A safe way to escape a string for a JavaScript literal is to use json.dumps().
| html_content = html_content.replace("{{ CAMERA_TOKEN }}", token) | |
| import json | |
| html_content = html_content.replace("{{ CAMERA_TOKEN }}", json.dumps(token)[1:-1]) |
| # Fallback for local development tree | ||
| repo_root = pathlib.Path(__file__).resolve().parent.parent.parent.parent.parent.parent.parent | ||
| proto_path = repo_root / "proto" / "prusa" / "connect" / "client" / "camera_v2.proto" |
There was a problem hiding this comment.
The fallback logic to find the .proto file by chaining .parent seven times is very fragile and will break if the directory structure changes. A more robust method should be used to locate the project root in a development environment.
Consider searching upwards from the current file for a project marker like pyproject.toml.
Here's a more robust implementation suggestion:
# Fallback for local development tree
current_path = pathlib.Path(__file__).resolve()
# Search for a file that indicates the project root, e.g., 'pyproject.toml'
repo_root = current_path
while not (repo_root / 'pyproject.toml').exists() and repo_root.parent != repo_root:
repo_root = repo_root.parent
if not (repo_root / 'pyproject.toml').exists():
common.output_message("Could not determine project root for development fallback.", error=True)
return
proto_path = repo_root / "proto" / "prusa" / "connect" / "client" / "camera_v2.proto"| // Wait 500ms for auth to process, then send triggers and request CONFIG | ||
| setTimeout(() => { |
There was a problem hiding this comment.
Using a fixed setTimeout to wait for authentication introduces a race condition. If authentication takes longer than 500ms due to network latency or server load, the subsequent trigger and webrtc events will be sent prematurely and may fail. A more reliable approach is to wait for an explicit confirmation event from the server (e.g., an authenticated or ready event) before proceeding with the next steps in the connection sequence.
| # Save to temp file and open | ||
| import tempfile | ||
|
|
||
| temp_file = pathlib.Path(tempfile.gettempdir()) / f"prusa_webrtc_{token}.html" |
There was a problem hiding this comment.
The camera webrtc command is vulnerable to Path Traversal. The user-provided camera_id (as token) is used to construct the filename for a temporary HTML file. An attacker could provide a camera_id containing path traversal sequences (e.g., ../../) to attempt to write the HTML file to an arbitrary location on the filesystem.
Remediation: Use a securely generated random filename for the temporary file instead of incorporating user-controlled input.
| temp_file = pathlib.Path(tempfile.gettempdir()) / f"prusa_webrtc_{token}.html" | |
| import uuid | |
| temp_file = pathlib.Path(tempfile.gettempdir()) / f"prusa_webrtc_{uuid.uuid4().hex}.html" |
| if hasattr(client, "_credentials") and hasattr(client._credentials, "tokens"): | ||
| jwt_token = client._credentials.tokens.access_token.raw_token |
There was a problem hiding this comment.
Accessing private members like _credentials is not a safe practice as it relies on internal implementation details that can change without notice, potentially breaking your code in future SDK versions. It would be more robust to add a public method to the PrusaConnectClient for retrieving the JWT, for example client.get_raw_jwt().
| # logger.debug("Initial login request", method="GET", params=params, | ||
| # url=consts.AUTH_URL) | ||
| resp = session.get(consts.AUTH_URL, params=params) | ||
| # logger.debug("Initial login flow reponse", status_code=resp.status_code, | ||
| # text=resp.text, headers=resp.headers) |
There was a problem hiding this comment.
| if hasattr(client, "_credentials") and hasattr(client._credentials, "tokens"): | ||
| jwt_token = client._credentials.tokens.access_token.raw_token |
There was a problem hiding this comment.
Accessing private members like _credentials and tokens is brittle and can break with future SDK updates. The comment "We reach into private credentials safely" is misleading, as this pattern is inherently unsafe. A much better approach would be to expose a public method on PrusaConnectClient for this purpose, such as get_raw_jwt().
- Add WebRTC signaling message definitions to `camera_v2.proto`. - Refactor `PrusaCameraClient` in `camera.py` to use a single `webrtc` event listener and add a `webrtc_send` method for handling protobuf-encoded signaling messages. - Add new `camera webrtc` CLI command to generate and open a local WebRTC player for the camera stream. - Add `webrtc_template.html` template that implements the WebRTC signaling flow (STUN/TURN config, SDP offer/answer, ICE candidates) using socket.io and protobuf.js to stream the camera video directly in the browser. - Bundle camera_v2.proto into package to inject into web template. - Update documentation to cover both SDK and CLI usage of webrtc feature.
This PR introduces comprehensive support for low-latency WebRTC camera streaming directly from the CLI and SDK, allowing users and developers to natively view and integrate live camera feeds from their Prusa 3D printers.
We've bridged the gap between the Prusa Connect WebRTC signaling server and local visualization, drastically simplifying the workflow necessary to view real-time video outputs.
Key Features & Changes
CLI camera webrtc Command
prusactl camera webrtc <camera-id>command that dynamically handles pulling camera tokensand SDK JWT credentials.
configuration payloads alongside the local
camera_v2.protodefinitions to construct a client-ready WebRTC viewer.Google-Style Docstrings Adoption
to ensure our API contracts are fully illuminated for community developers.
MkDocs Integration and Guides
prusactl camera webrtcright in the flowof the broader Work with Teams and Cameras segment.
inside docs/examples.md to equip developers with the snippet they need to bypass the CLI and extract exactly
the
camera_token,jwt_token, and proto configurations via thePrusaConnectClient.Testing & Verification
camera.pymkdocs build --strictlocally to guarantee no broken links and high-quality docstring parsing.