Home

/sse-webnotepad-python

import asyncio
from pathlib import Path

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, StreamingResponse
import uvicorn

app = FastAPI()

NOTE_FILE = Path("note.txt")
NOTE_FILE.touch(exist_ok=True)

clients = set()


HTML = """
<!DOCTYPE html>
<html>
<head>
    <title>SSE Notepad</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 900px;
            margin: 20px auto;
        }

        textarea {
            width: 100%;
            height: 500px;
            font-size: 16px;
            padding: 10px;
            box-sizing: border-box;
        }
    </style>
</head>
<body>
    <h2>Live Notepad (SSE)</h2>

    <textarea id="editor"></textarea>

    <script>
        const editor = document.getElementById("editor");
        let suppressSend = false;

        fetch("/content")
            .then(r => r.text())
            .then(text => {
                editor.value = text;
            });

        const events = new EventSource("/events");

        events.onmessage = (event) => {
            const incoming = event.data;

            if (editor.value !== incoming) {
                suppressSend = true;
                editor.value = incoming;
                suppressSend = false;
            }
        };

        let timer;

        editor.addEventListener("input", () => {
            if (suppressSend) return;

            clearTimeout(timer);

            timer = setTimeout(() => {
                fetch("/update", {
                    method: "POST",
                    headers: {
                        "Content-Type": "text/plain"
                    },
                    body: editor.value
                });
            }, 150);
        });
    </script>
</body>
</html>
"""


@app.get("/", response_class=HTMLResponse)
async def index():
    return HTML


@app.get("/content")
async def content():
    return NOTE_FILE.read_text(encoding="utf-8")


@app.post("/update")
async def update(request: Request):
    text = (await request.body()).decode("utf-8")

    NOTE_FILE.write_text(text, encoding="utf-8")

    for queue in list(clients):
        await queue.put(text)

    return {"status": "ok"}


@app.get("/events")
async def events():
    queue = asyncio.Queue()
    clients.add(queue)

    async def event_stream():
        try:
            yield f"data: {NOTE_FILE.read_text(encoding='utf-8')}\n\n"

            while True:
                data = await queue.get()

                data = (
                    data.replace("\\", "\\\\")
                        .replace("\\r", "")
                        .replace("\\n", "\\ndata: ")
                )

                yield f"data: {data}\n\n"

        finally:
            clients.discard(queue)

    return StreamingResponse(
        event_stream(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        },
    )


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)