Compare commits
10 Commits
4570923287
...
8740802bc6
Author | SHA1 | Date | |
---|---|---|---|
8740802bc6 | |||
9e1ecbbe1f | |||
03c51e7dbe | |||
15c405da6e | |||
9295c91b03 | |||
b9d9988550 | |||
4eb10d33cf | |||
5553334654 | |||
eff637ffa5 | |||
2906fbc529 |
28
chat-app/build.ts
Normal file
28
chat-app/build.ts
Normal 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.
71
chat-app/frontend/index.js
Normal file
71
chat-app/frontend/index.js
Normal 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);
|
14
chat-app/frontend/login.js
Normal file
14
chat-app/frontend/login.js
Normal 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" })
|
||||||
|
]);
|
||||||
|
};
|
2
chat-app/frontend/state.js
Normal file
2
chat-app/frontend/state.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
const state = {};
|
||||||
|
|
3
chat-app/frontend/websocket.js
Normal file
3
chat-app/frontend/websocket.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function createWebSocket() {
|
||||||
|
return new WebSocket("ws://localhost:3000/chat-service");
|
||||||
|
}
|
8
chat-app/justfile
Normal file
8
chat-app/justfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
build:
|
||||||
|
bun build.ts
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -r static/js/*
|
||||||
|
|
||||||
|
test:
|
||||||
|
bun test
|
@ -1,10 +1,15 @@
|
|||||||
{
|
{
|
||||||
"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"
|
||||||
|
@ -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();
|
||||||
|
@ -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" }));
|
||||||
|
23
chat-app/static/css/main.css
Normal file
23
chat-app/static/css/main.css
Normal 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%;
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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
1783
chat-app/static/js/index.js
Normal file
File diff suppressed because it is too large
Load Diff
22
notes.md
22
notes.md
@ -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?
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user