Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ dependencies = [
"nodeenv>=1.8.0",
"psutil>=5.9.5",
"yourdfpy>=0.0.53",
"plyfile>=1.0.2"
"plyfile>=1.0.2",
"cryptography>=44.0.2",
]

[project.optional-dependencies]
Expand Down
50 changes: 50 additions & 0 deletions src/viser/_certificates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import tempfile


def create_self_signed_cert():
"""Generates a self-signed SSL certificate and key."""
import datetime

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID

key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
subject = issuer = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Fake SSL"),
x509.NameAttribute(NameOID.COMMON_NAME, "localhost"),
]
)
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.sign(key, hashes.SHA256())
)

cert_path = tempfile.NamedTemporaryFile(delete=False, suffix=".crt").name
key_path = tempfile.NamedTemporaryFile(delete=False, suffix=".key").name

with open(cert_path, "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))

with open(key_path, "wb") as f:
f.write(
key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption(),
)
)

return cert_path, key_path
32 changes: 26 additions & 6 deletions src/viser/_viser.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from . import _client_autobuild, _messages, infra
from . import transforms as tf
from ._certificates import create_self_signed_cert
from ._gui_api import Color, GuiApi, _make_uuid
from ._notification_handle import NotificationHandle, _NotificationHandleState
from ._scene_api import SceneApi, cast_vector
Expand Down Expand Up @@ -588,6 +589,8 @@ class ViserServer(_BackwardsCompatibilityShim if not TYPE_CHECKING else object):
host: Host to bind server to.
port: Port to bind server to.
label: Label shown at the top of the GUI panel.
https: Whether to use HTTPS with self-signed certificates. If True,
server will use HTTPS and WSS protocols.
"""

# Hide deprecated arguments from docstring and type checkers.
Expand All @@ -597,8 +600,20 @@ def __init__(
port: int = 8080,
label: str | None = None,
verbose: bool = True,
https: bool = False,
**_deprecated_kwargs,
):
ssl_context = None
if https:
# Create Self Signed Certificates
cert_path, key_path = create_self_signed_cert()

# Create SSL Context
import ssl

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(cert_path, key_path)

# Create server.
server = infra.WebsockServer(
host=host,
Expand All @@ -607,6 +622,7 @@ def __init__(
http_server_root=Path(__file__).absolute().parent / "client" / "build",
verbose=verbose,
client_api_version=1,
ssl_context=ssl_context,
)
self._websock_server = server

Expand Down Expand Up @@ -728,19 +744,23 @@ def request_share_url_no_return() -> None: # To suppress type error.
# 0.0.0.0 is not a real IP and people are often confused by it;
# we'll just print localhost. This is questionable from a security
# perspective, but probably fine for our use cases.
http_url = f"http://localhost:{port}"
ws_url = f"ws://localhost:{port}"
http_protocol = "https" if https else "http"
ws_protocol = "wss" if https else "ws"
http_url = f"{http_protocol}://localhost:{port}"
ws_url = f"{ws_protocol}://localhost:{port}"
else:
http_url = f"http://{host}:{port}"
ws_url = f"ws://{host}:{port}"
http_protocol = "https" if https else "http"
ws_protocol = "wss" if https else "ws"
http_url = f"{http_protocol}://{host}:{port}"
ws_url = f"{ws_protocol}://{host}:{port}"
table = Table(
title=None,
show_header=False,
box=box.MINIMAL,
title_style=style.Style(bold=True),
)
table.add_row("HTTP", http_url)
table.add_row("Websocket", ws_url)
table.add_row(http_protocol.upper(), http_url)
table.add_row(ws_protocol.upper(), ws_url)
rich.print(
Panel(
table,
Expand Down
1 change: 1 addition & 0 deletions src/viser/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@msgpack/msgpack": "^3.0.0-beta2",
"@react-three/drei": "^9.64.0",
"@react-three/fiber": "^8.12.0",
"@react-three/xr": "^6.6.9",
"@tabler/icons-react": "^3.1.0",
"@types/node": "^20.11.30",
"@types/react": "^18.0.33",
Expand Down
32 changes: 22 additions & 10 deletions src/viser/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ import { AutoShadowDirectionalLight } from "./ThreeAssets";

THREE.ColorManagement.enabled = true;

// VR related imports
import { Canvas } from "@react-three/fiber";
import { XR, createXRStore } from "@react-three/xr";
import { useState } from "react";

const store = createXRStore();
// End VR related imports

function ViewerRoot() {
// What websocket server should we connect to?
function getDefaultServerFromUrl() {
Expand Down Expand Up @@ -242,6 +250,8 @@ function ViewerCanvas({ children }: { children: React.ReactNode }) {
height: "100%",
}}
>
<button onClick={() => store.enterVR()}>Enter VR</button>
<button onClick={() => store.enterAR()}>Enter AR</button>
<Canvas
camera={{ position: [-3.0, 3.0, -3.0], near: 0.01, far: 1000.0 }}
gl={{ preserveDrawingBuffer: true }}
Expand Down Expand Up @@ -399,16 +409,18 @@ function ViewerCanvas({ children }: { children: React.ReactNode }) {
}}
shadows
>
{inView ? null : <DisableRender />}
<BackgroundImage />
<SceneContextSetter />
{memoizedCameraControls}
<SplatRenderContext>
<AdaptiveDpr />
{children}
<SceneNodeThreeObject name="" parent={null} />
</SplatRenderContext>
<DefaultLights />
<XR store={store}>
{inView ? null : <DisableRender />}
<BackgroundImage />
<SceneContextSetter />
{memoizedCameraControls}
<SplatRenderContext>
<AdaptiveDpr />
{children}
<SceneNodeThreeObject name="" parent={null} />
</SplatRenderContext>
<DefaultLights />
</XR>
</Canvas>
</div>
);
Expand Down
1 change: 0 additions & 1 deletion src/viser/client/vite.config.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";

import viteTsconfigPaths from "vite-tsconfig-paths";
import svgrPlugin from "vite-plugin-svgr";
import eslint from "vite-plugin-eslint";
Expand Down
Loading