starting to put together sockets and two-way communication

This commit is contained in:
Shoofle 2024-10-08 23:24:48 -04:00
parent cadf1f3075
commit 940fc15b90
26 changed files with 313 additions and 157 deletions

View File

@ -23,6 +23,7 @@
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"react-sigma": "^1.2.35", "react-sigma": "^1.2.35",
"react-use-websocket": "^4.9.0",
"sigma": "^3.0.0-beta.29", "sigma": "^3.0.0-beta.29",
"vite": "^5.4.7", "vite": "^5.4.7",
"vite-tsconfig-paths": "^5.0.1", "vite-tsconfig-paths": "^5.0.1",
@ -3151,6 +3152,11 @@
"react-dom": ">=15.3" "react-dom": ">=15.3"
} }
}, },
"node_modules/react-use-websocket": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.9.0.tgz",
"integrity": "sha512-/6OaCMggQCTnryCAsw/N+/wfH7bBfIXk5WXTMPdyf0x9HWJXLGUVttAT5hqAimRytD1dkHEJCUrFHAGzOAg1eg=="
},
"node_modules/redent": { "node_modules/redent": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",

View File

@ -18,6 +18,7 @@
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"react-sigma": "^1.2.35", "react-sigma": "^1.2.35",
"react-use-websocket": "^4.9.0",
"sigma": "^3.0.0-beta.29", "sigma": "^3.0.0-beta.29",
"vite": "^5.4.7", "vite": "^5.4.7",
"vite-tsconfig-paths": "^5.0.1", "vite-tsconfig-paths": "^5.0.1",

View File

@ -1,12 +1,12 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Landing from '/src/landing/Landing.jsx'; import Landing from '/src/landing/Landing.jsx';
import Page from '/src/page/Page.jsx';
import PageWithSidebar from '/src/page/PageWithSidebar.jsx';
import HistoryView from '/src/page/HistoryView.jsx'; import HistoryView from '/src/page/HistoryView.jsx';
import LogIn from '/src/login/LogIn.jsx'; import LogIn from '/src/login/LogIn.jsx';
import Register from '/src/login/Register.jsx'; import Register from '/src/login/Register.jsx';
import Profile from '/src/login/Profile.jsx'; import Profile from '/src/login/Profile.jsx';
import GhostPage from '/src/page/GhostPage.jsx';
import Live from '/src/embodied/Live.jsx';
import AuthProvider from '/src/AuthProvider.jsx'; import AuthProvider from '/src/AuthProvider.jsx';
@ -23,11 +23,12 @@ function App() {
<Route path="/" element={<Landing/>}/> <Route path="/" element={<Landing/>}/>
<Route path="/login" element={<LogIn/>}/> <Route path="/login" element={<LogIn/>}/>
<Route path="/register" element={<Register/>}/> <Route path="/register" element={<Register/>}/>
<Route path="/:pagenumber" element={<PageWithSidebar/>}/> <Route path="/:pagenumber" element={<GhostPage/>}/>
<Route path="/:pagenumber/edit" element={<PageWithSidebar editing="true"/>}/> <Route path="/:pagenumber/edit" element={<GhostPage editing="true"/>}/>
<Route path="/:pagenumber/history" element={<HistoryView/>}/> <Route path="/:pagenumber/history" element={<HistoryView/>}/>
<Route path="/:pagenumber/:editid" element={<Page/>}/> <Route path="/:pagenumber/:editid" element={<GhostPage/>}/>
<Route path="/profile" element={<Profile/>}/> <Route path="/profile" element={<Profile/>}/>
<Route path="/live" element={<Live/>}/>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
</AuthProvider> </AuthProvider>

View File

@ -3,7 +3,11 @@ import { LogInStatusUpdateEscapeTool } from './AuthProvider.jsx';
// This is wrapper functtions to do requests to the api, from the frontend. // This is wrapper functtions to do requests to the api, from the frontend.
export const apiUrl = `${window.location.origin}/api`; const secure = false
export const apiUrl = `http${secure?'s':''}://${window.location.host}/api`;
export const wsUrl = `ws${secure?'s':''}://${window.location.host}/api`
//lil helper to throw errorrs from the promise when we get not-ok results //lil helper to throw errorrs from the promise when we get not-ok results

View File

@ -0,0 +1,12 @@
import './CommandEntry.css';
function CommandEntry({command, onChange, onSubmit, ...props}) {
return (
<div className="commandline">
<input type="text" placeholder="Enter a command!" value={command} onChange={onChange} {...props}/>
<button onClick={() => onSubmit(command)}>Do</button>
</div>
);
}
export default CommandEntry;

View File

@ -0,0 +1,60 @@
import { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams, useNavigate } from 'react-router-dom';
import { apiUrl, wsUrl } from '../apiTools.jsx';
import { useFixLinks } from '../clientStuff.jsx';
import { useLoggedIn } from '../AuthProvider.jsx';
import useWebSocket, { ReadyState } from 'react-use-websocket';
import Page from '../page/Page.jsx';
import MessageFeed from './MessageFeed.jsx';
import Sidebar from './Sidebar.jsx';
import CommandEntry from './CommandEntry.jsx';
function Live({editing, ...props}) {
const navigate = useNavigate();
const loggedIn = useLoggedIn();
const { pagenumber } = useParams();
const [command, setCommand] = useState("");
const [ messageHistory, setMessageHistory ] = useState([]);
const [ currentNumber, setCurrentNumber ] = useState(1);
const [ connecting, setConnecting ] = useState(true);
const { sendMessage, lastMessage, readyState } = useWebSocket(`${wsUrl}/embody`, {
onClose: () => setConnecting(false)
}, connecting);
useEffect(() => {
if (lastMessage !== null) {
setMessageHistory((prev) => prev.concat(lastMessage));
}
}, [lastMessage]);
useEffect(() => { sendMessage("what the fuk is up"); }, []);
function handleSendMessage() {
console.log("sending a message...");
sendMessage("button got clicked");
}
return (
<>
<Page editing={editing} number={currentNumber} {...props}/>
<MessageFeed messages={messageHistory}>
<button disabled={readyState!=ReadyState.OPEN} onClick={handleSendMessage}>send a message to the server!</button>
<button disabled={connecting} onClick={() => setConnecting(true)}>Reconnect</button>
</MessageFeed>
<Sidebar pagenumber={pagenumber} hidden="true" sendWord={(word) => setCommand((command + " " + word).trim())}>
{!editing && <li><button onClick={() => navigate(`/${pagenumber}/edit`)}>edit</button></li>}
{editing && <li><button disabled={postMutation.isPending} onClick={submitChanges}>save</button></li>}
{editing && <li><button onClick={() => navigate(`/${pagenumber}`)}>return</button></li>}
</Sidebar>
<CommandEntry
command={command}
onChange={(e) => setCommand(e.target.value)}
onSubmit={(e) => { setCommand(""); sendMessage(e); }}/>
</>
);
}
export default Live;

View File

@ -0,0 +1,25 @@
.messages-tray {
transition: all 0.1s linear;
margin: 0;
background: lightblue;
position: fixed;
width: 30ch;
height: 100%;
left: 0;
top: 0;
}
.messages-hidden {
transform: translateX(-20ch);
}
.sidebar li {
list-style: none;
}
.sidebar li button {
text-transform: uppercase;
}
.sidebar-hidden li {
display: none;
}

View File

@ -0,0 +1,27 @@
import { useEffect, useState, useCallback } from 'react';
import { useForm } from "react-hook-form";
import { useNavigate } from 'react-router-dom';
import './MessageFeed.css';
function MessageFeed({messages=[], children}) {
const [ open, setOpen ] = useState(false);
return (
<div className={`messages-tray ${!open?'messages-hidden':''}`}>
<button style={{float: 'right'}} onClick={() => setOpen(!open)}>{open?'Hide':'Show'}</button>
<h2>Message history:</h2>
<ol>
{messages.map((message, idx) =>
<li key={idx}>
{message.data}
<code style={{background: "lightgrey"}}>{JSON.stringify(message)}</code>
</li>
)}
</ol>
{children}
</div>
);
}
export default MessageFeed;

View File

@ -48,6 +48,7 @@ function Landing() {
<button onClick={makeNewPage.mutate}>Dig new room!</button><br/> <button onClick={makeNewPage.mutate}>Dig new room!</button><br/>
<button onClick={postLogOut}>Log Out</button><br/> <button onClick={postLogOut}>Log Out</button><br/>
<button onClick={() => navigate('/profile')}>Profile</button><br/> <button onClick={() => navigate('/profile')}>Profile</button><br/>
<button onClick={() => navigate('/live')}>Embody!</button><br/>
</> </>
: :
<> <>

View File

@ -1,28 +1,28 @@
import { useEffect } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { fetchProfile } from '../apiTools.jsx'; import { fetchProfile } from '../apiTools.jsx';
import { useLoggedIn } from '../AuthProvider.jsx'; import { useLoggedIn } from '../AuthProvider.jsx';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import useWebSocket, { ReadyState } from 'react-use-websocket';
function Profile() { function Profile() {
const { register, handleSubmit } = useForm(); const { register, handleSubmit } = useForm();
const navigate = useNavigate(); const navigate = useNavigate();
const loggedIn = useLoggedIn(); const loggedIn = useLoggedIn();
useEffect(() => { if (!loggedIn) navigate('/'); }, [loggedIn]);
const { isPending, isError, error, data } = useQuery({ // fetch the currrent values const { isPending, isError, error, data } = useQuery({ // fetch the currrent values
queryKey: ['profile'], queryKey: ['profile'],
queryFn: fetchProfile, queryFn: fetchProfile,
retry: 1
}); });
useEffect(() => { if (!loggedIn) navigate('/'); }, [loggedIn]);
const { id, name, favoriteColor, leastFavoriteColor } = (data || {}); const { id, name, favoriteColor, leastFavoriteColor } = (data || {});
const playerObject = 30;
return ( return (
<div className="main-column"> <div className="main-column">
<section className="page-contents"> <section className="page-contents">
<h2>Profile page</h2>
<p>the page {isPending ? "is" : "isn't"} pending</p> <p>the page {isPending ? "is" : "isn't"} pending</p>
<p>there {isError ? "is" : "isn't"} an error</p> <p>there {isError ? "is" : "isn't"} an error</p>
<p>error is {error?.message}</p> <p>error is {error?.message}</p>
@ -47,6 +47,11 @@ function Profile() {
<input {...register("leastFavoriteColor")} value={leastFavoriteColor}/> <input {...register("leastFavoriteColor")} value={leastFavoriteColor}/>
<br/> <br/>
</label> </label>
<label>
Player object:
<input {...register("playerObject")} value={playerObject} disabled/>
<br/>
</label>
<button type="submit">Change those settings, bucko!!</button> <button type="submit">Change those settings, bucko!!</button>
</form> </form>
</section> </section>

View File

@ -1,13 +0,0 @@
import { useState } from 'react';
import './CommandEntry.css';
function CommandEntry({command, onChange}) {
return (
<div className="commandline">
<input type="text" placeholder="Enter a command!" value={command} onChange={onChange}/>
</div>
);
}
export default CommandEntry;

View File

@ -0,0 +1,19 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams, useNavigate } from 'react-router-dom';
import { apiUrl, fetchPage, fetchPageAtEdit, postPage, deletePage } from '../apiTools.jsx';
import { useFixLinks } from '../clientStuff.jsx';
import { useLoggedIn } from '../AuthProvider.jsx';
import Page from './Page.jsx';
import './Pages.css';
function GhostPage({editing, ...props}) {
const { pagenumber } = useParams();
return (
<Page number={pagenumber} editing={editing} {...props}/>
);
}
export default GhostPage;

View File

@ -6,10 +6,11 @@ import { useLoggedIn } from '../AuthProvider.jsx';
import './Pages.css'; import './Pages.css';
function Page({ editing }) { function Page({ editing, number }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const { pagenumber, editid } = useParams(); const { editid } = useParams();
const pagenumber = number
const loggedIn = useLoggedIn(); const loggedIn = useLoggedIn();
const noLoad = useFixLinks(); const noLoad = useFixLinks();

View File

@ -1,128 +0,0 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams, useNavigate } from 'react-router-dom';
import { apiUrl, fetchPage, fetchPageAtEdit, postPage, deletePage } from '../apiTools.jsx';
import { useFixLinks } from '../clientStuff.jsx';
import { useLoggedIn } from '../AuthProvider.jsx';
import Sidebar from './Sidebar.jsx';
import CommandEntry from './CommandEntry.jsx';
import './Pages.css';
function PageWithSidebar({ editing }) {
const queryClient = useQueryClient();
const navigate = useNavigate();
const { pagenumber, editid } = useParams();
const loggedIn = useLoggedIn();
const noLoad = useFixLinks();
const [command, setCommand] = useState("");
const fetchQuery = useQuery({ // fetch the currrent values
queryKey: ['page', pagenumber, editid],
queryFn: () => editid ? fetchPageAtEdit(pagenumber, editid) : fetchPage(pagenumber)
})
const postMutation = useMutation({ // for changing the value when we're done with it
mutationFn: ({id, title, description}) => postPage({id, title, description}),
onSettled: async (data, error, variables) => {
// Invalidate and refetch
await queryClient.invalidateQueries({ queryKey: ['page', variables.id, undefined] })
},
});
const deleteMutation = useMutation({ // for changing the value when we're done with it
mutationFn: (id) => deletePage(id),
onSettled: async (data, error, variables) => {
// Invalidate and refetch
await queryClient.invalidateQueries({ queryKey: ['pages'] })
},
});
const readyToShow = !(fetchQuery.error || fetchQuery.isPending);
let {id, title, description, html, time, author} = fetchQuery.data || {};
if (!title) title = "[no title]";
if (!html) html = "[body missing]";
if (!description) description = "[body missing]";
function submitChanges(e) {
const newTitle = document.querySelector('span').innerHTML;
const newText = document.querySelector('pre').innerHTML;
postMutation.mutate({
id: pagenumber,
title: newTitle,
description: newText
});
navigate(`/${pagenumber}`, {replace: true})
}
function submitDelete(e) {
e.preventDefault();
deleteMutation.mutate(pagenumber);
navigate(`/`);
}
return (
<>
<div className="main-column">
<header>
<h1>
<a href="/" {...noLoad}>🌳</a>
{pagenumber}.&nbsp;
<span
contentEditable={editing}
dangerouslySetInnerHTML={{__html: readyToShow ? title : "..." }} />
</h1>
{readyToShow && editid && `saved at ${time} by user #${author}`}
<hr/>
</header>
<section className="page-contents">
{ readyToShow ?
(
editing ?
<pre
contentEditable="true"
dangerouslySetInnerHTML={{__html: description}} />
:
<div
dangerouslySetInnerHTML={{__html: html}}
{...noLoad} />
)
:
"..."
}
</section>
<button
onClick={() => navigate(`/${pagenumber}/history`)}>
History
</button>
{editing && (
<button
disabled={postMutation.isPending}
onClick={submitChanges}>
{postMutation.isPending ? "Updating..." : "Update"}
</button>)}
{!editing && !editid && (
<button
disabled={!loggedIn}
onClick={() => navigate(`/${pagenumber}/edit`)}>
Edit Page
</button>)}
{loggedIn && (
<button
disabled={deleteMutation.isPending}
onClick={submitDelete}>
{deleteMutation.isPending ? "Deleting..." : "Delete this page and entire edit history (no backsies)"}
</button>)}
</div>
<Sidebar pagenumber={pagenumber} hidden="true" sendWord={(word) => setCommand((command + " " + word).trim())}>
{!editing && <li><button onClick={() => navigate(`/${pagenumber}/edit`)}>edit</button></li>}
{editing && <li><button disabled={postMutation.isPending} onClick={submitChanges}>save</button></li>}
{editing && <li><button onClick={() => navigate(`/${pagenumber}`)}>return</button></li>}
</Sidebar>
<CommandEntry command={command} onChange={(e) => setCommand(e.target.value)}/>
</>
);
}
export default PageWithSidebar;

View File

@ -1,5 +1,5 @@
function loginRequired(req, res, next) { function loginRequired(req, res, next) {
console.log("checkinig on req.session for auhetnticaion: ", req.session); //console.log("checkinig on req.session for auhetnticaion: ", req.session);
if (!req.session.name) { if (!req.session.name) {
return res.status(401).json({"error": "need to be logged in for that bucko"}); return res.status(401).json({"error": "need to be logged in for that bucko"});
} }

47
server/interpreter.js Normal file
View File

@ -0,0 +1,47 @@
const sqlite = require('better-sqlite3');
const db = new sqlite('the_big_db.db', { verbose: console.log });
var sockets = new Map();
function interpret(context, subject, command) {
const words = command.split(' ');
const verbs = findVerbs(context, subject);
// first word is either a subject or a verb. either way there must be a verb.
// check if the first word is in the list of verbs.
const [first, second, third, ...rest] = words;
if (second in verbs) {
executeVerb(verbs.get(second), first, third, ...rest);
} else {
executeVerb(verbs.get(first), subject, second, third, ...rest)
}
}
function findVerbs(location, actor) {
// check for verbs on actor
// check for verbs on objects in location
// check for verbs on location
const out = new Map();
out.set("look", 29);
return out;
}
function executeVerb(verb, subject, object, ...rest) {
lookUpObject(verb).fn(subject, object, ...rest)
}
const objectQuery = db.prepare('select * from pages where id=?');
function lookUpObject(id) {
// return objectQuery.get(id);
if (id == 30) return {name: "shoofle", contents: "this is a shoofle", location: 1};
if (id == 29) return {
name: "look",
contents: "send description of direct object to subject's socket",
fn: (subject, object, ...rest) => {
sockets.get(subject)?.send(`you looked around! subject: ${subject} object: ${object} args: ${rest}`);
console.log(`${subject} looked at ${object} with args ${rest}`);
}
};
}
module.exports = { interpret, sockets, lookUpObject };

View File

@ -16,6 +16,7 @@
"client-sessions": "^0.8.0", "client-sessions": "^0.8.0",
"express": "^4.21.0", "express": "^4.21.0",
"express-session": "^1.18.0", "express-session": "^1.18.0",
"express-ws": "^5.0.2",
"graphology": "^0.25.4", "graphology": "^0.25.4",
"graphology-layout": "^0.6.1", "graphology-layout": "^0.6.1",
"graphology-layout-force": "^0.2.4", "graphology-layout-force": "^0.2.4",
@ -815,6 +816,40 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
}, },
"node_modules/express-ws": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz",
"integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
"dependencies": {
"ws": "^7.4.6"
},
"engines": {
"node": ">=4.5.0"
},
"peerDependencies": {
"express": "^4.0.0 || ^5.0.0-alpha.1"
}
},
"node_modules/express-ws/node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/fetch-blob": { "node_modules/fetch-blob": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",

View File

@ -21,6 +21,7 @@
"client-sessions": "^0.8.0", "client-sessions": "^0.8.0",
"express": "^4.21.0", "express": "^4.21.0",
"express-session": "^1.18.0", "express-session": "^1.18.0",
"express-ws": "^5.0.2",
"graphology": "^0.25.4", "graphology": "^0.25.4",
"graphology-layout": "^0.6.1", "graphology-layout": "^0.6.1",
"graphology-layout-force": "^0.2.4", "graphology-layout-force": "^0.2.4",

View File

@ -12,6 +12,9 @@ app.use(page_routes);
const user_routes = require('./users.js'); const user_routes = require('./users.js');
app.use(user_routes); app.use(user_routes);
const socket_routes = require('./sockets.js');
app.use(socket_routes);
const graphQuery = db.prepare(` const graphQuery = db.prepare(`
select p.number, p.html, p.time select p.number, p.html, p.time
from pages p from pages p

42
server/routes/sockets.js Normal file
View File

@ -0,0 +1,42 @@
const express = require('express');
const app = express.Router();
const expressWs = require('express-ws')(app);
const sqlite = require('better-sqlite3');
const db = new sqlite('the_big_db.db', { verbose: console.log });
const { loginRequired } = require('../authStuff.js');
const { interpret, sockets, lookUpObject } = require('../interpreter.js');
const clockListeners = new Set();
const clock = setInterval(() => {
if (clockListeners.size == 0) return;
console.log(`sending a ping to all ${clockListeners.size} connections`);
clockListeners.forEach((x) => x());
}, 5000);
console.log(`set up the clock: ${clock}`);
app.ws('/embody', (ws, req) => {
// const { playerObject } = db.prepare('select playerObject from users where id=?').get(req.session.userId)
const playerObjectId = 30; // mocked out for now!
sockets.set(playerObjectId, ws);
ws.on('message', (msg) => {
const location = lookUpObject(playerObjectId).location;
interpret(location, playerObjectId, msg);
});
ws.on('close', () => sockets.delete(playerObjectId));
});
module.exports = app;

View File

@ -29,6 +29,7 @@ app.post('/register', async (req, res) => {
}); });
app.post('/login', async (req, res) => { app.post('/login', async (req, res) => {
console.log(req.body);
if (req.session.name) { if (req.session.name) {
return res.status(200).json({message: "already logged in", name: req.session.name}); return res.status(200).json({message: "already logged in", name: req.session.name});
} }

View File

@ -1,5 +1,6 @@
const express = require('express'); const express = require('express');
const app = express(); const app = express();
const expressWs = require('express-ws')(app);
const session = require('express-session'); const session = require('express-session');
@ -8,7 +9,7 @@ const apiRoutes = require('./routes/api.js');
const port = process.env.PORT || 3001; // Use the port provided by the host or default to 3000 const port = process.env.PORT || 3001; // Use the port provided by the host or default to 3000
const sqlite = require('better-sqlite3'); const sqlite = require('better-sqlite3');
const db = new sqlite('the_big_db.db', { verbose: console.log }); const db = new sqlite('the_big_db.db');
const SqliteStore = require('better-sqlite3-session-store')(session); const SqliteStore = require('better-sqlite3-session-store')(session);
app.use(express.json()); app.use(express.json());

View File

@ -5,6 +5,11 @@ server {
location /api/ { location /api/ {
proxy_pass http://localhost:3001/api/; proxy_pass http://localhost:3001/api/;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
} }
location / { location / {
proxy_pass http://localhost:3000/; proxy_pass http://localhost:3000/;