Added a weird little chat for shits and giggles

This commit is contained in:
2025-04-26 01:34:49 -06:00
parent a6a17e8969
commit 871000c333
9 changed files with 297 additions and 44 deletions

85
routes/api/chat.ts Normal file
View File

@ -0,0 +1,85 @@
import { FreshContext } from "$fresh/server.ts";
const chatConnections = new Set<WebSocket>();
// HTML sanitization
function sanitizeText(text: string): string {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// Process and sanitize message object
function processChatMessage(message: string): string {
try {
const parsed = JSON.parse(message);
if (typeof parsed.text === "string") {
parsed.text = sanitizeText(parsed.text.trim().slice(0, 2000));
}
if (typeof parsed.sender === "string") {
parsed.sender = sanitizeText(parsed.sender.trim().slice(0, 50));
}
if (!parsed.timestamp) {
parsed.timestamp = new Date().toISOString();
}
return JSON.stringify(parsed);
} catch (error) {
console.error("Invalid message format:", error);
return JSON.stringify({
text: "Error: Invalid message format",
sender: "System",
timestamp: new Date().toISOString(),
});
}
}
// Broadcast current user count to all clients
function broadcastUserCount() {
const count = chatConnections.size;
const message = JSON.stringify({
type: "user_count",
count: count,
});
for (const client of chatConnections) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
}
}
export const handler = (req: Request, _ctx: FreshContext): Response => {
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () => {
chatConnections.add(socket);
console.log(`New connection: ${chatConnections.size} users connected`);
broadcastUserCount();
};
// Handle messages
socket.onmessage = (event) => {
const sanitizedMessage = processChatMessage(event.data);
for (const client of chatConnections) {
if (client.readyState === WebSocket.OPEN) {
client.send(sanitizedMessage);
}
}
};
socket.onclose = () => {
chatConnections.delete(socket);
console.log(`Connection closed: ${chatConnections.size} users connected`);
broadcastUserCount();
};
return response;
};

View File

@ -1,21 +0,0 @@
import { FreshContext } from "$fresh/server.ts";
// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/
const JOKES = [
"Why do Java developers often wear glasses? They can't C#.",
"A SQL query walks into a bar, goes up to two tables and says “can I join you?”",
"Wasn't hard to crack Forrest Gump's password. 1forrest1.",
"I love pressing the F5 key. It's refreshing.",
"Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”",
"There are 10 types of people in the world. Those who understand binary and those who don't.",
"Why are assembly programmers often wet? They work below C level.",
"My favourite computer based band is the Black IPs.",
"What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.",
"An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.",
];
export const handler = (_req: Request, _ctx: FreshContext): Response => {
const randomIndex = Math.floor(Math.random() * JOKES.length);
const body = JOKES[randomIndex];
return new Response(body);
};

5
routes/api/ping.ts Normal file
View File

@ -0,0 +1,5 @@
import { FreshContext } from "$fresh/server.ts";
export const handler = (_req: Request, _ctx: FreshContext): Response => {
return new Response("pong");
};

20
routes/chat.tsx Normal file
View File

@ -0,0 +1,20 @@
import Chat from "../islands/Chat.tsx";
export default function ChatPage() {
return (
<div class="min-h-screen p-4 sm:p-8">
<h1 class="text-3xl sm:text-4xl font-bold text-secondary mb-6 sm:mb-8 text-center">
Chat Room <div className="badge badge-dash badge-primary">Demo</div>
</h1>
<div class="max-w-4xl mx-auto">
<Chat />
</div>
<div class="mt-8 text-center text-sm text-gray-500">
<p>
This is an ephemeral chat room. Messages are only visible to users
currently online and aren't stored after you leave.
</p>
</div>
</div>
);
}

View File

@ -1,5 +1,4 @@
import { CSS, render } from "@deno/gfm";
import { Head } from "$fresh/runtime.ts";
import { render } from "@deno/gfm";
import { Handlers, PageProps } from "$fresh/server.ts";
import { getPost, Post } from "../../lib/posts.ts";
@ -21,9 +20,6 @@ export default function PostPage(props: PageProps<Post>) {
const post = props.data;
return (
<>
<Head>
<style dangerouslySetInnerHTML={{ __html: CSS }} />
</Head>
<div class="min-h-screen p-4 md:p-8">
<div class="max-w-3xl mx-auto">
<div class="p-4 md:p-8">
@ -73,6 +69,7 @@ export default function PostPage(props: PageProps<Post>) {
class="max-w-none prose"
data-color-mode="dark"
data-dark-theme="dark"
// deno-lint-ignore react-no-danger
dangerouslySetInnerHTML={{ __html: render(post.content) }}
/>
</div>