Compare commits
9
Commits
97a71ba6c2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d57c58a3bd | ||
|
|
a647cd5184 | ||
|
|
9758ae6dad | ||
|
|
8b2de757ac | ||
|
|
4fdace24b7 | ||
|
|
b8c110b53b | ||
|
|
c8df253758 | ||
|
|
d24997dea9 | ||
|
|
9525548253 |
@@ -23,7 +23,17 @@ jobs:
|
|||||||
key: bun-${{ hashFiles('bun.lockb') }}
|
key: bun-${{ hashFiles('bun.lockb') }}
|
||||||
- run: bun install --frozen-lockfile
|
- run: bun install --frozen-lockfile
|
||||||
- run: bun run test
|
- run: bun run test
|
||||||
- run: bun run test:e2e
|
e2e:
|
||||||
|
runs-on: ubuntu-bun-deno
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.bun/install/cache
|
||||||
|
key: bun-${{ hashFiles('bun.lockb') }}
|
||||||
|
- run: bun install --frozen-lockfile
|
||||||
|
- run: bunx playwright install --with-deps
|
||||||
|
- run: bunx playwright test
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
+2
-1
@@ -17,7 +17,8 @@
|
|||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noTsIgnore": "off",
|
"noTsIgnore": "off",
|
||||||
"noAssignInExpressions": "off",
|
"noAssignInExpressions": "off",
|
||||||
"noFallthroughSwitchClause": "warn"
|
"noFallthroughSwitchClause": "warn",
|
||||||
|
"noGlobalAssign": "warn"
|
||||||
},
|
},
|
||||||
"correctness": {
|
"correctness": {
|
||||||
"useParseIntRadix": "off",
|
"useParseIntRadix": "off",
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
"name": "nage",
|
"name": "nage",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.5.14",
|
"@biomejs/biome": "2.5.14",
|
||||||
"@playwright/test": "^1.63.0",
|
"@playwright/test": "1.63.0",
|
||||||
"@types/bun": "1.4.2",
|
"@types/bun": "1.4.2",
|
||||||
"@types/node": "^26.6.3",
|
"@types/node": "26.6.3",
|
||||||
"jsdom": "^30.1.1",
|
"jsdom": "30.1.1",
|
||||||
"lz-string": "1.5.0",
|
"lz-string": "1.5.0",
|
||||||
"playwright": "^1.63.0",
|
"playwright": "1.63.0",
|
||||||
"solid-js": "1.9.15",
|
"solid-js": "1.9.15",
|
||||||
"typescript": "7.0.2",
|
"typescript": "7.0.2",
|
||||||
"vite": "8.3.1",
|
"vite": "8.3.1",
|
||||||
"vite-plugin-solid": "2.11.14",
|
"vite-plugin-solid": "2.11.14",
|
||||||
"vitest": "^5.0.2",
|
"vitest": "5.0.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": "deno run -A npm:vite",
|
||||||
|
"build": "deno run -A npm:vite build",
|
||||||
|
"preview": "deno run -A npm:vite build && deno run -A npm:vite preview",
|
||||||
|
"test": "deno run -A npm:vitest",
|
||||||
|
"test:e2e": "deno run -A npm:playwright test",
|
||||||
|
"check": "deno run -A npm:@biomejs/biome check --fix",
|
||||||
|
"serve": {
|
||||||
|
"command": "tbd",
|
||||||
|
"description": "tbd",
|
||||||
|
"dependencies": ["tbd"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"#/": "./"
|
||||||
|
},
|
||||||
|
"nodeModulesDir": "auto",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext", "deno.ns"]
|
||||||
|
},
|
||||||
|
"exclude": ["/", "!server"]
|
||||||
|
}
|
||||||
@@ -85,3 +85,12 @@ function animate(timestamp: number): void {
|
|||||||
|
|
||||||
animate(delta);
|
animate(delta);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
|
||||||
|
type: "module",
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.onmessage = (event) => {
|
||||||
|
console.log(event.data);
|
||||||
|
};
|
||||||
|
worker.postMessage("foo");
|
||||||
|
|||||||
+1
-1
@@ -25,6 +25,6 @@
|
|||||||
"vitest": "5.0.2"
|
"vitest": "5.0.2"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"#/": "./*"
|
"#/": "./"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-5
@@ -1,15 +1,58 @@
|
|||||||
/// <reference lib="deno.ns" />
|
|
||||||
import { assertEquals } from "jsr:@std/assert";
|
import { assertEquals } from "jsr:@std/assert";
|
||||||
|
import { createSession } from "#/server/handler.ts";
|
||||||
import handler from "./handler.ts";
|
import handler from "./handler.ts";
|
||||||
import { close, connect, flush, href, setup } from "./testSetup.ts";
|
import { close, connect, flush, hostname, port, setup } from "./testSetup.ts";
|
||||||
|
|
||||||
|
const href = `ws://${hostname}:${port}`;
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
"test",
|
"needs upgrade header",
|
||||||
setup(async () => {
|
setup(async () => {
|
||||||
const t1 = "123-abc";
|
const res = await fetch(`http://${hostname}:${port}`);
|
||||||
const [s1, m1] = await connect(`${href}?ticket=${t1}`);
|
assertEquals(res.status, 426);
|
||||||
|
assertEquals(res.statusText, "Upgrade Required");
|
||||||
|
}, handler),
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
"needs a session parameter",
|
||||||
|
setup(async () => {
|
||||||
|
const res = await connect(href).catch((reason) => {
|
||||||
|
return reason;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
res.message,
|
||||||
|
"NetworkError: failed to connect to WebSocket: Invalid status code: 400",
|
||||||
|
);
|
||||||
|
}, handler),
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
"needs an existing session",
|
||||||
|
setup(async () => {
|
||||||
|
const res = await connect(`${href}?sessionId=DOES_NOT_EXISTS`).catch(
|
||||||
|
(reason) => {
|
||||||
|
return reason;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
res.message,
|
||||||
|
"NetworkError: failed to connect to WebSocket: Invalid status code: 401",
|
||||||
|
);
|
||||||
|
}, handler),
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
"connects to an existing session",
|
||||||
|
setup(async () => {
|
||||||
|
const sessionId = createSession();
|
||||||
|
const [s1, m1] = await connect(`${href}?sessionId=${sessionId}`);
|
||||||
|
|
||||||
await flush();
|
await flush();
|
||||||
assertEquals(m1, []);
|
assertEquals(m1, []);
|
||||||
|
|
||||||
await close(s1);
|
await close(s1);
|
||||||
}, handler),
|
}, handler),
|
||||||
);
|
);
|
||||||
|
|||||||
+55
-25
@@ -1,15 +1,11 @@
|
|||||||
import { STATUS_CODE, STATUS_TEXT, type StatusCode } from "jsr:@std/http";
|
import { STATUS_CODE, STATUS_TEXT, type StatusCode } from "jsr:@std/http";
|
||||||
|
|
||||||
const ALLOWED_ORIGIN = "http://0.0.0.0:8042";
|
type Session = {
|
||||||
|
id: string;
|
||||||
const sessions = new Map<string, WebSocket>();
|
connId: number;
|
||||||
|
createdAt: number;
|
||||||
function createSession(socket: WebSocket): string {
|
lastSeenAt: number;
|
||||||
const session = crypto.randomUUID();
|
};
|
||||||
sessions.set(session, socket);
|
|
||||||
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createResponse(
|
function createResponse(
|
||||||
status: StatusCode,
|
status: StatusCode,
|
||||||
@@ -18,32 +14,66 @@ function createResponse(
|
|||||||
return new Response(body, { status, statusText: STATUS_TEXT[status] });
|
return new Response(body, { status, statusText: STATUS_TEXT[status] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sessions = new Map<string, Session>();
|
||||||
|
const connections = new Map<number, WebSocket>();
|
||||||
|
|
||||||
|
export function createSession(): string {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
const now = Date.now();
|
||||||
|
sessions.set(id, {
|
||||||
|
id,
|
||||||
|
connId: 0,
|
||||||
|
createdAt: now,
|
||||||
|
lastSeenAt: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentId = 1;
|
||||||
|
const nextId = (): number => {
|
||||||
|
return currentId++;
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectSession = (session: Session, request: Request): Response => {
|
||||||
|
const { response, socket } = Deno.upgradeWebSocket(request);
|
||||||
|
const connId = nextId();
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
session.lastSeenAt = Date.now();
|
||||||
|
session.connId = connId;
|
||||||
|
connections.set(connId, socket);
|
||||||
|
};
|
||||||
|
socket.onclose = () => {
|
||||||
|
session.lastSeenAt = Date.now();
|
||||||
|
connections.delete(connId);
|
||||||
|
};
|
||||||
|
socket.onmessage = () => {
|
||||||
|
session.lastSeenAt = Date.now();
|
||||||
|
};
|
||||||
|
socket.onerror = (e) => {
|
||||||
|
session.lastSeenAt = Date.now();
|
||||||
|
console.error(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
export default function (request: Request): Response {
|
export default function (request: Request): Response {
|
||||||
if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") {
|
if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") {
|
||||||
return createResponse(STATUS_CODE.UpgradeRequired);
|
return createResponse(STATUS_CODE.UpgradeRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
if (url.origin !== ALLOWED_ORIGIN) {
|
const sessionId = url.searchParams.get("sessionId");
|
||||||
|
if (sessionId === null) {
|
||||||
return createResponse(STATUS_CODE.BadRequest);
|
return createResponse(STATUS_CODE.BadRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionId = url.searchParams.get("sessionId");
|
|
||||||
|
|
||||||
if (sessionId === null) {
|
|
||||||
const upgrade = Deno.upgradeWebSocket(request);
|
|
||||||
createSession(upgrade.socket);
|
|
||||||
|
|
||||||
return upgrade.response;
|
|
||||||
} else {
|
|
||||||
const session = sessions.get(sessionId);
|
const session = sessions.get(sessionId);
|
||||||
if (session === null) {
|
if (session === undefined) {
|
||||||
return createResponse(STATUS_CODE.Unauthorized);
|
return createResponse(STATUS_CODE.Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
return connectSession(session, request);
|
||||||
// I need a new socket every time
|
|
||||||
}
|
|
||||||
|
|
||||||
return createResponse(STATUS_CODE.InternalServerError);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/// <reference lib="deno.ns" />
|
|
||||||
|
|
||||||
export function startServer(
|
export function startServer(
|
||||||
handler: Deno.ServeHandler,
|
handler: Deno.ServeHandler,
|
||||||
hostname: string = "localhost",
|
hostname: string = "localhost",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { startServer } from "./server.ts";
|
|||||||
|
|
||||||
export const hostname = "0.0.0.0";
|
export const hostname = "0.0.0.0";
|
||||||
export const port = 8042;
|
export const port = 8042;
|
||||||
export const href = `ws://${hostname}:${port}`;
|
|
||||||
|
|
||||||
export function setup(
|
export function setup(
|
||||||
fn: (ctx: Deno.TestContext) => Promise<void>,
|
fn: (ctx: Deno.TestContext) => Promise<void>,
|
||||||
|
|||||||
+7
-1
@@ -18,7 +18,13 @@ function devTools(): Plugin {
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: { host, port, hmr: false },
|
server: { host, port, hmr: false },
|
||||||
build: { target: "esnext" },
|
build: {
|
||||||
|
target: "esnext",
|
||||||
|
main: `${root}index.html`,
|
||||||
|
lib: {
|
||||||
|
entry: `${root}worker.ts`,
|
||||||
|
},
|
||||||
|
},
|
||||||
test: {
|
test: {
|
||||||
include: ["./engine/**/*.test.ts"],
|
include: ["./engine/**/*.test.ts"],
|
||||||
environment: "jsdom",
|
environment: "jsdom",
|
||||||
|
|||||||
Reference in New Issue
Block a user