Compare commits

...

10 Commits

15 changed files with 1997 additions and 63 deletions

28
chat-app/build.ts Normal file
View File

@ -0,0 +1,28 @@
async function main() {
const results = await Bun.build({
entrypoints: [ "./frontend/index.js" ],
outdir: "./static/js/",
verbose: true
});
if (!results.success) {
for (const message of results.logs) {
console.error(message);
}
return false;
}
return true;
}
main()
.then(() => {
process.exit(0)
})
.catch((e) => {
console.error(e);
process.exit(1);
});

Binary file not shown.

View File

@ -0,0 +1,71 @@
import m from "mithril";
import { createWebSocket } from "./websocket";
// on new message event, call m.redraw();
const messages = [];
const socket = createWebSocket();
socket.onmessage = function (event) {
if (event.type === "message") {
const message = JSON.parse(event.data);
messages.push(message);
m.redraw();
}
}
function newMessageText(name, message) {
return [
m("span.chatter", name + ": "),
m("span.chatter-message", message)
];
}
const MessageBox = {
view: function () {
return m(".messages", messages.map(function(message) {
return m(".message", newMessageText(message.chatter, message.message));
}));
}
};
function clearMessageBox() {
const messageBox = document.getElementById("message");
messageBox.value = "";
}
function sendMessage(e) {
e.preventDefault();
const form = new FormData(e.target);
const message = {};
for (const [key, value] of form.entries()) {
message[key] = value;
}
socket.send(JSON.stringify(message));
clearMessageBox();
}
const MessageInput = {
view: function () {
return m("div.chat-input", [
m("form", { method: "post", onsubmit: sendMessage }, [
m("input", { name: "chatter", placeholder: "Name" }),
m("input", { name: "message", placeholder: "Message", id: "message", required: true }),
m("input", { type: "submit", value: "Send" })
])
]);
}
};
const Chat = {
view: function(vnode) {
return m("div", [
m(MessageBox),
m(MessageInput)
])
}
};
m.mount(document.getElementById("app"), Chat);

View File

@ -0,0 +1,14 @@
import m from 'mithril';
// login to the server
function login(e) {
}
export const Login = {
return m("form", { action: "/login", method: "post", onsubmit: login }, [
m("label", { for: "username" }, "Username"),
m("input", { type: "text", name: "username" }),
m("input", { type: "submit", value: "Login" })
]);
};

View File

@ -0,0 +1,2 @@
const state = {};

View File

@ -0,0 +1,3 @@
export function createWebSocket() {
return new WebSocket("ws://localhost:3000/chat-service");
}

8
chat-app/justfile Normal file
View File

@ -0,0 +1,8 @@
build:
bun build.ts
clean:
rm -r static/js/*
test:
bun test

View File

@ -1,12 +1,17 @@
{ {
"name": "chat-app", "name": "chat-app",
"scripts": { "scripts": {
"dev": "bun run --hot src/index.ts" "dev": "bun run --hot src/index.ts",
"build": "rm -r static/js; bun build.ts"
}, },
"dependencies": { "dependencies": {
"hono": "^4.6.3" "@types/mithril": "^2.2.7",
"hono": "^4.6.3",
"js-cookie": "^3.0.5",
"js-guid": "^1.0.2",
"mithril": "^2.2.5"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest" "@types/bun": "latest"
} }
} }

View File

@ -8,8 +8,8 @@ export const chatters = new Array<Chatter>();
export type Message = { export type Message = {
message: string, message: string,
timestamp: Date timestamp: Date,
chatter: Chatter chatter: string,
}; };
export function createMessageString(message: Message): string { export function createMessageString(message: Message): string {
@ -20,7 +20,8 @@ export class ChatRoom extends EventEmitter {
protected messages: Message[]; protected messages: Message[];
public constructor() { public constructor() {
this.message = new Array<Message>(); super();
this.messages = new Array<Message>();
} }
public getMessages(): Message[] { public getMessages(): Message[] {
@ -39,6 +40,11 @@ export class ChatRoom extends EventEmitter {
public getNewestMessage(): Message { public getNewestMessage(): Message {
const message = this.messages[this.messages - 1]; const message = this.messages[this.messages - 1];
} }
// set up the lifetime to clear messages in memory
protected pruneMessages(lifetime: number): void {
}
} }
export const chatRomm = new ChatRoom(); export const chatRoom = new ChatRoom();

View File

@ -4,44 +4,55 @@ import type { ServerWebSocket } from "hono";
import { import {
chatRoom, chatRoom,
createMessageString, createMessageString,
type Chatter,
type Message type Message
} from "./chat-room.ts"; } from "./chat-room.ts";
const app = new Hono(); const app = new Hono();
const users: string[] = [];
// Set up the chat socket // Set up the chat socket
const { upgradeWebSocket, websocket } = createBunWebSocket<ServerWebSocket>(); const { upgradeWebSocket, websocket } = createBunWebSocket<ServerWebSocket>();
app.get("/chat-socket", upgradeWebSocket((c) => { app.get("/chat-service", upgradeWebSocket((c) => {
return { return {
onOpen(_event, ws) {
chatRoom.addListener('message-added', function (e) {
ws.send(JSON.stringify({
message: e.message.message,
chatter: e.message.chatter,
timestamp: e.message.timestamp
}));
});
},
onMessage(event, ws) { onMessage(event, ws) {
const data = JSON.parse(event.data); const { data } = event;
const message = JSON.parse(data);
if (!chatters.find(c => c.name == data.chatter.name)) { message.timestamp = Date.now() as string;
chatters.push(data.chatter);
}
const message: Message = {
chatter: { data.chatter },
message: data.message,
timestamp: Date.now() as string;
};
chatRoom.addMessage(message); chatRoom.addMessage(message);
ws.send(JSON.stringify({ message: createMessageString(message) }));
}, },
onClose() { onClose() {
console.log("Connection closed."); console.log("Connection closed.");
}, },
onError(event) { onError(event, ws) {
console.log(event); console.error(event);
ws.close();
} }
} }
})); }));
// set up the username, somehow
// set the session for a user
app.post("/login", async (c) => {
const user = await c.req.parseBody();
let success: boolean = false;
if (!users.find((u) => u == user.username)) {
users.push(user.username);
success = true;
}
return c.json({ success });
});
// get the HTML and JS from the static repo // get the HTML and JS from the static repo
app.use("/", serveStatic({ path: "/static/index.html" })); app.use("/", serveStatic({ path: "/static/index.html" }));
app.use("/*", serveStatic({ root: "/static" })); app.use("/*", serveStatic({ root: "/static" }));

View File

@ -0,0 +1,23 @@
.messages .message {
padding: 0.5em;
border: 1px solid #aaa;
border-radius: 5px;
margin-bottom: 0.5em;
}
.chatter {
font-weight: bold;
}
.chat-input > form {
display: flex;
width: 100%;
}
.chat-input > form > input {
padding: 0.7em;
}
.chat-input > form > input[name="chatter"] {
width: 20%;
}
.chat-input > form > input[name="message"] {
width: 75%;
}

View File

@ -2,19 +2,10 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>A Dumb Chat Application</title> <title>A Dumb Chat Application</title>
<link rel="stylesheet" type="text/css" href="/css/main.css" />
</head> </head>
<body> <body>
<div id="app"></div>
<script src="https://unpkg.com/mithril/mithril.js"></script> <script src="/js/index.js"></script>
<script src="/js/chat-service.js"></script>
<script>
// start the chat web socket
initChatService();
// render the application
const root = document.body;
m.render(root, m("h1", "A Stupid Chat"));
</script>
</body> </body>
</html> </html>

View File

@ -1,17 +0,0 @@
function initChatService() {
const ws = new WebSocket("ws://localhost:3000/chat-socket");
ws.onmessage = function (event) {
console.log(event);
};
ws.onopen = function (event) {
console.log("Opening the socket");
ws.send(JSON.stringify({ hello: "world" }));
};
ws.onclose = function (event) {
console.log("Closing the socket");
};
ws.onerror = function (event) {
console.log(event);
};
}

1783
chat-app/static/js/index.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,17 +13,23 @@ Deploy the talk on the blog as well
## The Dumb Idea ## The Dumb Idea
Simple application that does something, like a guess the number or tik-tak-toe A simple Chat application built with Hono and Mithril.js
Something with a frontend and backend to talk about deploying multiple applications.
Or build it with React
A simple twitte clone ## Talk About the History
Mesages on a timeline, living in memory.
Multiplayer tic-tac-toe? What was my first deployment?
Chat?
Using the file manager in cPanel.
Then using SVN on the server.
Upgrading to Git, on the server.
A lot WHM
Developing with Vagrant and virtual machines
Moving to AWS, EC2
Using VPS providers like DigitalOcean
Building Container images
## What is Deployment ## What is Deployment
@ -41,7 +47,7 @@ show some diagrams
Talk about the SSL cert situation Talk about the SSL cert situation
Talk about building VM images or containers for deployment Talk about building VM images or containers for deployment
"What about K8s?" I don't know or really care "What about K8s?" I don't know or really care, honestly
## What does Dokku do for us? ## What does Dokku do for us?