remove websockets examples in favor of socketio

Commit b677ab3 · patx · 2025-01-28T04:29:07-05:00

Changeset
b677ab39ab67c195161520adaf081e2fb2c4619a
Parents
e35050474fc64a39daca8569bc310d60ad5b2703

View source at this commit

Comments

No comments yet.

Log in to comment

Diff

diff --git a/examples/streaming/video.mp4 b/examples/streaming/video.mp4
deleted file mode 100644
index 9b89f62..0000000
Binary files a/examples/streaming/video.mp4 and /dev/null differ
diff --git a/examples/websockets/MicroPie.py b/examples/websockets/MicroPie.py
deleted file mode 100644
index ad02b3d..0000000
--- a/examples/websockets/MicroPie.py
+++ /dev/null
@@ -1,442 +0,0 @@
-"""
-MicroPie: A simple Python ultra-micro web framework with ASGI
-support. https://patx.github.io/micropie
-
-Copyright Harrison Erd
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice,
-   this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright notice,
-   this list of conditions and the following disclaimer in the documentation
-   and/or other materials provided with the distribution.
-
-3. Neither the name of the copyright holder nor the names of its
-   contributors may be used to endorse or promote products derived from this
-   software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
-EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-import time
-import uuid
-import inspect
-import os
-import mimetypes
-from urllib.parse import parse_qs
-from typing import Optional, Dict, Any, Union, Tuple, List
-
-try:
-    from jinja2 import Environment, FileSystemLoader
-    JINJA_INSTALLED = True
-except ImportError:
-    JINJA_INSTALLED = False
-
-
-class Server:
-    SESSION_TIMEOUT: int = 8 * 3600  # 8 hours
-
-    def __init__(self) -> None:
-        if JINJA_INSTALLED:
-            self.env = Environment(loader=FileSystemLoader("templates"))
-
-        self.sessions: Dict[str, Dict[str, Any]] = {}
-        self.query_params: Dict[str, List[str]] = {}
-        self.body_params: Dict[str, List[str]] = {}
-        self.path_params: List[str] = []
-        self.session: Dict[str, Any] = {}
-        self.files: Dict[str, Any] = {}
-
-    async def __call__(self, scope, receive, send):
-        await self.asgi_app(scope, receive, send)
-
-    async def asgi_app(self, scope: Dict[str, Any], receive: Any, send: Any) -> None:
-        """ASGI application entrypoint for both HTTP and WebSockets."""
-
-        if scope["type"] == "websocket":
-            # Example approach for route-based WebSocket handler lookup:
-            path = scope["path"].lstrip("/")
-            self.scope = scope
-            path_parts = path.split("/") if path else []
-            func_name = path_parts[0] if path_parts else "default"
-            self.path_params = path_parts[1:] if len(path_parts) > 1 else []
-
-            # Use a naming convention such as websocket_<func_name>.
-            ws_handler_name = f"websocket_{func_name}"
-            handler_function = getattr(self, ws_handler_name, None)
-
-            if not handler_function:
-                await self._websocket_default(scope, receive, send)
-                return
-
-            try:
-                await handler_function(scope, receive, send)
-            except Exception:
-                await send({"type": "websocket.close", "code": 1011})
-            return
-
-        elif scope["type"] == "http":
-            self.scope = scope
-            method = scope["method"]
-            path = scope["path"].lstrip("/")
-            path_parts = path.split("/") if path else []
-            func_name = path_parts[0] if path_parts else "index"
-            self.path_params = path_parts[1:] if len(path_parts) > 1 else []
-
-            handler_function = getattr(self, func_name, None)
-            if not handler_function:
-                self.path_params = path_parts
-                handler_function = getattr(self, "index", None)
-
-            raw_query = scope.get("query_string", b"")
-            self.query_params = parse_qs(raw_query.decode("utf-8", "ignore"))
-
-            headers_dict = {
-                k.decode("latin-1").lower(): v.decode("latin-1")
-                for k, v in scope.get("headers", [])
-            }
-            cookies = self._parse_cookies(headers_dict.get("cookie", ""))
-
-            session_id = cookies.get("session_id")
-            if session_id and session_id in self.sessions:
-                self.session = self.sessions[session_id]
-                self.session["last_access"] = time.time()
-            else:
-                session_id = str(uuid.uuid4())
-                self.session = {"last_access": time.time()}
-                self.sessions[session_id] = self.session
-
-            self.body_params = {}
-            self.files = {}
-            if method in ("POST", "PUT", "PATCH"):
-                body_data = bytearray()
-                while True:
-                    msg = await receive()
-                    if msg["type"] == "http.request":
-                        body_data += msg.get("body", b"")
-                        if not msg.get("more_body"):
-                            break
-                content_type = headers_dict.get("content-type", "")
-                if "multipart/form-data" in content_type:
-                    self.parse_multipart(bytes(body_data), content_type)
-                else:
-                    body_str = body_data.decode("utf-8", "ignore")
-                    self.body_params = parse_qs(body_str)
-
-            sig = inspect.signature(handler_function)
-            func_args = []
-            for param in sig.parameters.values():
-                if self.path_params:
-                    func_args.append(self.path_params.pop(0))
-                elif param.name in self.query_params:
-                    func_args.append(self.query_params[param.name][0])
-                elif param.name in self.body_params:
-                    func_args.append(self.body_params[param.name][0])
-                elif param.name in self.files:
-                    func_args.append(self.files[param.name])
-                elif param.name in self.session:
-                    func_args.append(self.session[param.name])
-                elif param.default is not param.empty:
-                    func_args.append(param.default)
-                else:
-                    await self._send_response(
-                        send,
-                        status_code=400,
-                        body=f"400 Bad Request: Missing required parameter '{param.name}'",
-                    )
-                    return
-
-            if handler_function == getattr(self, "index", None) and not func_args and path:
-                await self._send_response(send, status_code=404, body="404 Not Found")
-                return
-
-            try:
-                if inspect.iscoroutinefunction(handler_function):
-                    result = await handler_function(*func_args)
-                else:
-                    result = handler_function(*func_args)
-            except Exception as e:
-                print(f"Error processing request: {e}")
-                await self._send_response(
-                    send, status_code=500, body="500 Internal Server Error"
-                )
-                return
-
-            status_code = 200
-            response_body = result
-            extra_headers: List[Tuple[str, str]] = []
-
-            if isinstance(result, tuple):
-                if len(result) == 2:
-                    status_code, response_body = result
-                elif len(result) == 3:
-                    status_code, response_body, extra_headers = result
-                else:
-                    await self._send_response(
-                        send, status_code=500,
-                        body="500 Internal Server Error: Invalid response tuple"
-                    )
-                    return
-
-            session_cookie_header = (
-                "Set-Cookie",
-                f"session_id={session_id}; Path=/; HttpOnly; SameSite=Strict"
-            )
-            has_session_cookie = any(
-                h[0].lower() == "set-cookie" and "session_id=" in h[1]
-                for h in extra_headers
-            )
-            if not has_session_cookie:
-                extra_headers.append(session_cookie_header)
-
-            await self._send_response(
-                send,
-                status_code=status_code,
-                body=response_body,
-                extra_headers=extra_headers
-            )
-
-    async def _websocket_default(self, scope: Dict[str, Any], receive: Any, send: Any):
-        """Default WebSocket handler if no match is found."""
-        await send({"type": "websocket.accept"})
-        await send({"type": "websocket.close", "code": 1000})
-
-    def _parse_cookies(self, cookie_header: str) -> Dict[str, str]:
-        cookies: Dict[str, str] = {}
-        if not cookie_header:
-            return cookies
-        for cookie in cookie_header.split(";"):
-            if "=" in cookie:
-                k, v = cookie.strip().split("=", 1)
-                cookies[k] = v
-        return cookies
-
-    def parse_multipart(self, body: bytes, content_type: str) -> None:
-        boundary = None
-        parts = content_type.split(";")
-        for part in parts:
-            part = part.strip()
-            if part.startswith("boundary="):
-                boundary = part.split("=", 1)[1]
-                break
-
-        if not boundary:
-            raise ValueError("Boundary not found in Content-Type header.")
-
-        boundary_bytes = boundary.encode("utf-8")
-        delimiter = b"--" + boundary_bytes
-        sections = body.split(delimiter)
-        for section in sections:
-            if not section or section in (b"--", b"--\r\n"):
-                continue
-            if section.startswith(b"\r\n"):
-                section = section[2:]
-            if section.endswith(b"\r\n"):
-                section = section[:-2]
-            if section == b"--":
-                continue
-
-            try:
-                headers, content = section.split(b"\r\n\r\n", 1)
-            except ValueError:
-                continue
-
-            headers_list = headers.decode("utf-8", "ignore").split("\r\n")
-            header_dict = {}
-            for header_line in headers_list:
-                if ":" in header_line:
-                    key, value = header_line.split(":", 1)
-                    header_dict[key.strip().lower()] = value.strip()
-
-            disposition = header_dict.get("content-disposition", "")
-            disposition_parts = disposition.split(";")
-            disposition_dict = {}
-            for disp_part in disposition_parts:
-                if "=" in disp_part:
-                    k, v = disp_part.strip().split("=", 1)
-                    disposition_dict[k] = v.strip('"')
-
-            name = disposition_dict.get("name")
-            filename = disposition_dict.get("filename")
-
-            if filename:
-                file_content_type = header_dict.get("content-type", "application/octet-stream")
-                self.files[name] = {
-                    "filename": filename,
-                    "content_type": file_content_type,
-                    "data": content
-                }
-            elif name:
-                value = content.decode("utf-8", "ignore")
-                if name in self.body_params:
-                    self.body_params[name].append(value)
-                else:
-                    self.body_params[name] = [value]
-
-    async def _send_response(
-        self,
-        send,
-        status_code: int,
-        body,
-        extra_headers=None
-    ):
-        if extra_headers is None:
-            extra_headers = []
-
-        # Common HTTP status text
-        status_map = {
-            200: "200 OK",
-            206: "206 Partial Content",
-            302: "302 Found",
-            403: "403 Forbidden",
-            404: "404 Not Found",
-            500: "500 Internal Server Error",
-        }
-        # Fallback if not in map
-        status_text = status_map.get(status_code, f"{status_code} OK")
-
-        # Ensure there's a Content-Type unless already provided
-        has_content_type = any(h[0].lower() == "content-type" for h in extra_headers)
-        if not has_content_type:
-            extra_headers.append(("Content-Type", "text/html; charset=utf-8"))
-
-        # Send the initial response start
-        await send({
-            "type": "http.response.start",
-            "status": status_code,
-            "headers": [
-                (k.encode("latin-1"), v.encode("latin-1")) for k, v in extra_headers
-            ],
-        })
-
-        #
-        # -- Begin CHUNKED/STREAMING logic --
-        #
-        # 1) Check if body is an async generator (has __aiter__)
-        if hasattr(body, "__aiter__"):
-            async for chunk in body:
-                if isinstance(chunk, str):
-                    chunk = chunk.encode("utf-8")
-                await send({
-                    "type": "http.response.body",
-                    "body": chunk,
-                    "more_body": True
-                })
-            # Send a final empty chunk to mark the end
-            await send({
-                "type": "http.response.body",
-                "body": b"",
-                "more_body": False
-            })
-            return
-
-        # 2) Check if body is a *sync* generator (has __iter__) and
-        #    is not a plain string/bytes
-        if hasattr(body, "__iter__") and not isinstance(body, (bytes, str)):
-            for chunk in body:
-                if isinstance(chunk, str):
-                    chunk = chunk.encode("utf-8")
-                await send({
-                    "type": "http.response.body",
-                    "body": chunk,
-                    "more_body": True
-                })
-            # Send a final empty chunk
-            await send({
-                "type": "http.response.body",
-                "body": b"",
-                "more_body": False
-            })
-            return
-
-        #
-        # -- Fallback for normal (non-generator) body --
-        #
-        if isinstance(body, str):
-            response_body = body.encode("utf-8")
-        elif isinstance(body, bytes):
-            response_body = body
-        else:
-            # Convert anything else to string then to bytes
-            response_body = str(body).encode("utf-8")
-
-        await send({
-            "type": "http.response.body",
-            "body": response_body,
-            "more_body": False
-        })
-
-    def cleanup_sessions(self) -> None:
-        now = time.time()
-        self.sessions = {
-            sid: data
-            for sid, data in self.sessions.items()
-            if data.get("last_access", now) + self.SESSION_TIMEOUT > now
-        }
-
-    def redirect(self, location: str) -> Tuple[int, str]:
-        return (
-            302,
-            (
-                "<html><head>"
-                f"<meta http-equiv='refresh' content='0;url={location}'>"
-                "</head></html>"
-            ),
-        )
-
-    def render_template(self, name: str, **kwargs: Any) -> str:
-        if not JINJA_INSTALLED:
-            raise ImportError("Jinja2 is not installed.")
-        return self.env.get_template(name).render(kwargs)
-
-    def serve_static(
-        self, filepath: str
-    ) -> Union[Tuple[int, str], Tuple[int, bytes, List[Tuple[str, str]]]]:
-        safe_root = os.path.abspath("static")
-        requested_file = os.path.abspath(os.path.join("static", filepath))
-        if not requested_file.startswith(safe_root):
-            return 403, "403 Forbidden"
-        if not os.path.isfile(requested_file):
-            return 404, "404 Not Found"
-        content_type, _ = mimetypes.guess_type(requested_file)
-        if not content_type:
-            content_type = "application/octet-stream"
-        with open(requested_file, "rb") as f:
-            content = f.read()
-        return 200, content, [("Content-Type", content_type)]
-
-    def validate_request(self, method: str) -> bool:
-        try:
-            if method == "GET":
-                for key, value in self.query_params.items():
-                    if (
-                        not isinstance(key, str)
-                        or not all(isinstance(v, str) for v in value)
-                    ):
-                        return False
-
-            if method == "POST":
-                for key, value in self.body_params.items():
-                    if (
-                        not isinstance(key, str)
-                        or not all(isinstance(v, str) for v in value)
-                    ):
-                        return False
-
-            return True
-        except:
-            return False
-
diff --git a/examples/websockets/chatroom.py b/examples/websockets/chatroom.py
deleted file mode 100644
index ed00b9b..0000000
--- a/examples/websockets/chatroom.py
+++ /dev/null
@@ -1,118 +0,0 @@
-from typing import Dict, Set, Any
-from MicroPie import Server
-
-# We track who is "active" (registered a username)
-active_users: Set[str] = set()
-
-# We store a dictionary of channels -> sets of websocket send handles
-# For a single chat room, we only need one channel, e.g. "global".
-watchers: Dict[str, Set[Any]] = {}
-
-class ChatApp(Server):
-    async def index(self):
-        """
-        Serve a simple form where the user enters a username.
-        """
-        return self.render_template("index_chat.html")
-
-    async def submit(self, username: str):
-        """
-        Handle the POST from index.html where user enters their name.
-        Then redirect them to /chat/<username>.
-        """
-        username = username.strip()
-        if not username:
-            return self.redirect("/")  # Invalid username, go back
-
-        # Mark this user as active
-        active_users.add(username)
-        return self.redirect(f"/chat/{username}")
-
-    async def chat(self, username: str):
-        """
-        Show the chat page if the user is active; otherwise, redirect home.
-        """
-        if username not in active_users:
-            return self.redirect("/")
-        return self.render_template("chat.html", username=username)
-
-    #
-    # ------------- WEBSOCKET HANDLER -------------
-    #
-    async def websocket_chat(self, scope, receive, send):
-        """
-        If the path is /chat/<username>, MicroPie calls this method
-        because 'websocket_{pathParts[0]}' = 'websocket_chat'.
-        """
-        # Extract the <username> from self.path_params
-        username = self.path_params[0] if self.path_params else None
-
-        # 1) Make sure username is valid and active
-        if not username or username not in active_users:
-            return await self._reject_websocket(send)
-
-        # 2) Wait for 'websocket.connect' before accepting
-        msg = await receive()
-        if msg["type"] == "websocket.connect":
-            await send({"type": "websocket.accept"})
-        else:
-            # If we didn’t get the connect message, close
-            return await self._reject_websocket(send)
-
-        # 3) Add this connection to watchers["global"]
-        watchers.setdefault("global", set()).add(send)
-
-        # 4) Handle incoming messages until the client disconnects
-        try:
-            while True:
-                message = await receive()
-                if message["type"] == "websocket.receive":
-                    # If there's text, broadcast it
-                    if "text" in message:
-                        text_msg = message["text"]
-                        await self._broadcast("global", f"{username}: {text_msg}", is_binary=False)
-                    elif "bytes" in message:
-                        byte_data = message["bytes"]
-                        await self._broadcast("global", byte_data, is_binary=True)
-                elif message["type"] == "websocket.disconnect":
-                    break
-        finally:
-            # Remove the user's send handle from watchers["global"]
-            watchers["global"].discard(send)
-            # If watchers["global"] is empty, remove the channel
-            if not watchers["global"]:
-                del watchers["global"]
-
-    #
-    # ------------- HELPER METHODS -------------
-    #
-    async def _reject_websocket(self, send):
-        """
-        Accept then immediately close with a custom code.
-        This is a simple approach to gracefully reject a WebSocket.
-        """
-        await send({"type": "websocket.accept"})
-        await send({"type": "websocket.close", "code": 4000})
-
-    async def _broadcast(self, channel: str, data, is_binary: bool = False):
-        """
-        Broadcast 'data' to all watchers in the given channel.
-        """
-        for ws_send in list(watchers.get(channel, [])):
-            try:
-                await ws_send({
-                    "type": "websocket.send",
-                    "bytes" if is_binary else "text": data
-                })
-            except:
-                # If sending fails, remove that watcher's socket
-                watchers[channel].discard(ws_send)
-                if not watchers[channel]:
-                    del watchers[channel]
-
-# Create the ASGI app instance
-app = ChatApp()
-
-# If you want to run via uvicorn:
-#   uvicorn chat_app:app --host 127.0.0.1 --port 5000
-
diff --git a/examples/websockets/templates/chat.html b/examples/websockets/templates/chat.html
deleted file mode 100644
index 4850fe1..0000000
--- a/examples/websockets/templates/chat.html
+++ /dev/null
@@ -1,68 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8" />
-  <title>MicroPie Chat - {{ username }}</title>
-  <style>
-    body {
-      font-family: sans-serif;
-      margin: 20px;
-    }
-    #chatBox {
-      border: 1px solid #ccc;
-      width: 500px;
-      height: 300px;
-      overflow-y: auto;
-      padding: 10px;
-      margin-bottom: 10px;
-    }
-    #messageInput {
-      width: 400px;
-    }
-  </style>
-</head>
-<body>
-  <h1>Chat Room (User: {{ username }})</h1>
-
-  <div id="chatBox"></div>
-  <input id="messageInput" type="text" placeholder="Type a message..." />
-  <button id="sendBtn">Send</button>
-
-  <script>
-    const username = "{{ username }}";
-    const chatBox = document.getElementById("chatBox");
-    const messageInput = document.getElementById("messageInput");
-    const sendBtn = document.getElementById("sendBtn");
-
-    // Build the WebSocket URL, using the current host but ws:// or wss://
-    const wsProto = (location.protocol === "https:") ? "wss://" : "ws://";
-    const wsUrl = wsProto + window.location.host + "/chat/" + encodeURIComponent(username);
-
-    const socket = new WebSocket(wsUrl);
-
-    socket.onmessage = (event) => {
-      // Display the new message
-      const msgDiv = document.createElement("div");
-      msgDiv.textContent = event.data;
-      chatBox.appendChild(msgDiv);
-      chatBox.scrollTop = chatBox.scrollHeight;
-    };
-
-    sendBtn.onclick = () => {
-      sendMessage();
-    };
-    messageInput.addEventListener("keydown", (e) => {
-      if (e.key === "Enter") sendMessage();
-    });
-
-    function sendMessage() {
-      const text = messageInput.value.trim();
-      if (text) {
-        socket.send(text);
-        messageInput.value = "";
-      }
-    }
-  </script>
-</body>
-</html>
-
diff --git a/examples/websockets/templates/index_chat.html b/examples/websockets/templates/index_chat.html
deleted file mode 100644
index 9422898..0000000
--- a/examples/websockets/templates/index_chat.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8" />
-  <title>MicroPie Chat - Home</title>
-</head>
-<body>
-  <h1>Welcome to MicroPie Chat!</h1>
-  <form method="post" action="/submit">
-    <label for="username">Enter a username:</label>
-    <input id="username" name="username" type="text" />
-    <button type="submit">Enter Chat</button>
-  </form>
-</body>
-</html>
-
diff --git a/examples/websockets/templates/index_stream.html b/examples/websockets/templates/index_stream.html
deleted file mode 100644
index b710858..0000000
--- a/examples/websockets/templates/index_stream.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Webcam Streaming</title>
-</head>
-<body>
-    <h1>Webcam Streaming App</h1>
-    <form method="post" action="/submit">
-        <input type="text" name="username" placeholder="Enter username" required>
-        <button type="submit" name="action" value="Start Streaming">Start Streaming</button>
-        <button type="submit" name="action" value="Watch Stream">Watch Stream</button>
-    </form>
-</body>
-</html>
-
diff --git a/examples/websockets/templates/stream.html b/examples/websockets/templates/stream.html
deleted file mode 100644
index b48da16..0000000
--- a/examples/websockets/templates/stream.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8" />
-  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-  <title>Streaming: {{ username }}</title>
-</head>
-<body>
-  <h1>Streaming as {{ username }}</h1>
-  <video id="webcam" autoplay playsinline></video>
-
-  <script>
-    const username = "{{ username }}";
-
-    // Create a WebSocket to your /stream/<username> route
-    const ws = new WebSocket(`ws://${window.location.host}/stream/${username}`);
-
-    // Wait for the connection to open
-    ws.onopen = function () {
-      console.log("Connected as streamer for", username);
-      startWebcam();
-    };
-
-    ws.onclose = function () {
-      console.log("WebSocket closed");
-    };
-
-    async function startWebcam() {
-      try {
-        // Capture the user's webcam
-        const stream = await navigator.mediaDevices.getUserMedia({ video: true });
-        const videoElement = document.getElementById('webcam');
-        videoElement.srcObject = stream;
-
-        // Create an offscreen canvas to grab frames
-        const canvas = document.createElement('canvas');
-        const context = canvas.getContext('2d');
-
-        // Adjust canvas size to match the video (or choose your own)
-        const [track] = stream.getVideoTracks();
-        const settings = track.getSettings();
-        canvas.width = settings.width || 640;
-        canvas.height = settings.height || 480;
-
-        setInterval(() => {
-          // Draw the current video frame to the canvas
-          context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
-
-          // Convert to base64-encoded image (data URL)
-          const frameDataUrl = canvas.toDataURL('image/webp');
-
-          // Send it via the WebSocket
-          if (ws.readyState === WebSocket.OPEN) {
-            ws.send(frameDataUrl);
-          }
-        }, 100); // ~10 FPS
-      } catch (err) {
-        console.error("Error accessing webcam:", err);
-      }
-    }
-  </script>
-</body>
-</html>
-
diff --git a/examples/websockets/templates/watch.html b/examples/websockets/templates/watch.html
deleted file mode 100644
index 007cf77..0000000
--- a/examples/websockets/templates/watch.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8" />
-  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-  <title>Watching: {{ username }}</title>
-</head>
-<body>
-  <h1>Watching Stream of {{ username }}</h1>
-  <img id="videoFeed" style="width: 100%; max-width: 800px;" />
-
-  <script>
-    const username = "{{ username }}";
-
-    // Create a WebSocket to your /watch/<username> route
-    const ws = new WebSocket(`ws://${window.location.host}/watch/${username}`);
-
-    // Called when the WebSocket connection is established
-    ws.onopen = function () {
-      console.log("Connected as watcher for", username);
-    };
-
-    // Called whenever the server broadcasts a new frame
-    ws.onmessage = function (event) {
-      // Here, event.data is expected to be a dataURL or other string
-      document.getElementById("videoFeed").src = event.data;
-    };
-
-    // Called when the WebSocket is closed (e.g., streamer goes offline)
-    ws.onclose = function () {
-      console.log("WebSocket closed");
-    };
-  </script>
-</body>
-</html>
diff --git a/examples/websockets/webcam.py b/examples/websockets/webcam.py
deleted file mode 100644
index 077c6d2..0000000
--- a/examples/websockets/webcam.py
+++ /dev/null
@@ -1,75 +0,0 @@
-from typing import Dict, Set, Any
-from MicroPie import Server
-
-# Keep track of active users (who have "started streaming")
-active_users: Set[str] = set()
-streamers: Dict[str, Set[Any]] = {}
-watchers: Dict[str, Set[Any]] = {}
-
-class MyApp(Server):
-
-    async def index(self):
-        return self.render_template("index_stream.html")
-
-    async def submit(self, username: str, action: str):
-        if username:
-            active_users.add(username)
-            route = f"/stream/{username}" if action == "Start Streaming" else f"/watch/{username}"
-            return self.redirect(route)
-        return self.redirect("/")
-
-    async def stream(self, username: str):
-        return self.render_template("stream.html", username=username) if username in active_users else self.redirect("/")
-
-    async def watch(self, username: str):
-        return self.render_template("watch.html", username=username) if username in active_users else self.redirect("/")
-
-    async def websocket_stream(self, scope, receive, send):
-        username = self.path_params[0] if self.path_params else None
-        if not username or username not in active_users:
-            return await self._reject_websocket(send)
-
-        await send({"type": "websocket.accept"})
-        streamers.setdefault(username, set()).add(send)
-
-        await self._handle_websocket(receive, send, username, streamers, is_stream=True)
-
-    async def websocket_watch(self, scope, receive, send):
-        username = self.path_params[0] if self.path_params else None
-        if not username or username not in active_users:
-            return await self._reject_websocket(send)
-
-        await send({"type": "websocket.accept"})
-        watchers.setdefault(username, set()).add(send)
-
-        await self._handle_websocket(receive, send, username, watchers)
-
-    async def _handle_websocket(self, receive, send, username, registry, is_stream=False):
-        try:
-            while True:
-                message = await receive()
-                if message["type"] == "websocket.receive":
-                    if "text" in message or "bytes" in message:
-                        await self._broadcast(username, message.get("text") or message.get("bytes"), is_binary="bytes" in message)
-                elif message["type"] == "websocket.disconnect":
-                    break
-        finally:
-            registry[username].discard(send)
-            if not registry[username]:
-                del registry[username]
-
-    async def _reject_websocket(self, send):
-        await send({"type": "websocket.accept"})
-        await send({"type": "websocket.close", "code": 4000})
-
-    async def _broadcast(self, username: str, data, is_binary: bool = False):
-        for ws_send in list(watchers.get(username, [])):
-            try:
-                await ws_send({"type": "websocket.send", "bytes" if is_binary else "text": data})
-            except:
-                watchers[username].discard(ws_send)
-                if not watchers[username]:
-                    del watchers[username]
-
-# Create the ASGI app
-app = MyApp()