diff --git a/src/app.html b/src/app.html index 67052a8..2edde36 100644 --- a/src/app.html +++ b/src/app.html @@ -7,7 +7,7 @@ diff --git a/src/app.pcss b/src/app.pcss index 57b54d3..64b0630 100644 --- a/src/app.pcss +++ b/src/app.pcss @@ -4,14 +4,6 @@ @tailwind components; @tailwind utilities; -@font-face { - font-family: "Roc Grotesk"; - src: url(./assets/fonts/RocGroteskWideMedium.ttf) format("truetype"); - font-weight: 500; - font-style: normal; - font-display: swap; -} - @font-face { font-family: "Consolas"; src: url(./assets/fonts/Consolas.ttf) format("truetype"); @@ -37,7 +29,7 @@ html { h1, h2, h3 { - font-family: "Roc Grotesk", sans-serif; + font-family: "Outfit", sans-serif; } ::selection { diff --git a/src/assets/fonts/RocGroteskWideMedium.ttf b/src/assets/fonts/RocGroteskWideMedium.ttf deleted file mode 100644 index f59a054..0000000 Binary files a/src/assets/fonts/RocGroteskWideMedium.ttf and /dev/null differ diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d623a31..2a96771 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -213,6 +213,14 @@ }); +
+ +
+
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3b92465..b479699 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -40,7 +40,7 @@
-

- suyu is an open-source, non-profit Switch emulator +

+ suyu is a fully open-source + Switch emulator

suyu is a familiar C++ based Switch emulator with a focus on compatibility. Completely free diff --git a/src/routes/account/+layout.svelte b/src/routes/account/+layout.svelte index a5f4877..ab03edf 100644 --- a/src/routes/account/+layout.svelte +++ b/src/routes/account/+layout.svelte @@ -22,7 +22,7 @@ const navItems: NavItem[] = [ { - name: "Online Services", + name: "Multiplayer", href: "/account", }, { @@ -30,8 +30,8 @@ href: "/account/lobbies", }, { - name: "Settings", - href: "/account/settings", + name: "Friends", + href: "/account/friends", }, ]; @@ -54,7 +54,11 @@ const pillBounds = indicator.getBoundingClientRect(); indicator.style.transform = `translateX(${bounds.left - navBounds.left}px)`; indicator.style.width = `${bounds.width}px`; - if ((selected !== 0 && selected !== navItems.length - 1) || $reducedMotion) return; + if ( + // (selected !== 0 && selected !== navItems.length - 1) || + $reducedMotion + ) + return; indicator.offsetHeight; const transformFactor = bounds.left - pillBounds.left; navBar.animate( @@ -64,23 +68,18 @@ easing: "ease-out", }, { - transform: `translateX(${transformFactor / 100}px)`, - offset: 0.1, + transform: `translateX(${transformFactor / 50}px)`, + offset: 0.4, easing: "ease-out", }, - { - transform: `translateX(${-transformFactor / 200}px)`, - offset: 0.8, - easing: "ease-in", - }, { transform: "translateX(0px)", easing: "ease-in", }, ], { - duration: 500, - delay: 170, + duration: 360, + delay: 0, }, ); } diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte index a4b2302..2854996 100644 --- a/src/routes/account/+page.svelte +++ b/src/routes/account/+page.svelte @@ -52,12 +52,14 @@ stroke="white" /> -

- suyu Online Services -

+

Multiplayer

- Your token should be kept private. If you believe it has been compromised, please - contact us immediately. + Hey, {data.user.username}! This is your token, used to authenticate your identity within suyu. Your token + should be kept private. If you believe it has been compromised, please contact us + immediately.

-
diff --git a/src/routes/account/friends/+page.svelte b/src/routes/account/friends/+page.svelte new file mode 100644 index 0000000..69efca5 --- /dev/null +++ b/src/routes/account/friends/+page.svelte @@ -0,0 +1,10 @@ +
+
+

Friends

+

+ TBD: madeline has no friends :( ~maddie/null +

+
+
diff --git a/src/routes/account/lobbies/+page.server.ts b/src/routes/account/lobbies/+page.server.ts new file mode 100644 index 0000000..c5d6ecf --- /dev/null +++ b/src/routes/account/lobbies/+page.server.ts @@ -0,0 +1,7 @@ +import { RoomManager } from "$lib/server/class/Room.js"; + +export function load({ request }) { + return { + rooms: RoomManager.getRooms().map((r) => r.toJSON()), + }; +} diff --git a/src/routes/account/lobbies/+page.svelte b/src/routes/account/lobbies/+page.svelte index c5d1511..b5b62c9 100644 --- a/src/routes/account/lobbies/+page.svelte +++ b/src/routes/account/lobbies/+page.svelte @@ -1,7 +1,15 @@ + +
- LOBBIES YEHAHH + {#each data.rooms as room} +

{room.name}

+ {/each}
diff --git a/src/routes/account/settings/+page.svelte b/src/routes/account/settings/+page.svelte deleted file mode 100644 index ee350be..0000000 --- a/src/routes/account/settings/+page.svelte +++ /dev/null @@ -1,7 +0,0 @@ -
-
- SETTTTTINGGGASSS WOOOOOOOOOOOO -
-
diff --git a/src/routes/api/user/+server.ts b/src/routes/api/user/+server.ts index e37b0ec..b53dc99 100644 --- a/src/routes/api/user/+server.ts +++ b/src/routes/api/user/+server.ts @@ -39,7 +39,7 @@ export async function POST({ request, getClientAddress }) { error: "missing fields", }); } - if (body.username.length < 3 || body.username.length > 24) { + if (body.username.length < 3 || body.username.length > 24 || body.username.trim() === "") { return json({ success: false, error: "invalid username", diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+layout.server.ts similarity index 92% rename from src/routes/blog/+page.server.ts rename to src/routes/blog/+layout.server.ts index bac9c59..ddd65ad 100644 --- a/src/routes/blog/+page.server.ts +++ b/src/routes/blog/+layout.server.ts @@ -36,9 +36,12 @@ export async function load({ request }) { const user = await useModeratorAuth(request); return { posts, - userInfo: JSON.parse(JSON.stringify(user)) as { + userInfo: (JSON.parse(JSON.stringify(user)) as { user: SuyuUser | null; isModerator: boolean; + }) || { + user: null, + isModerator: false, }, }; } diff --git a/src/routes/blog/new/+page.svelte b/src/routes/blog/new/+page.svelte index 8e8b122..7f28fc4 100644 --- a/src/routes/blog/new/+page.svelte +++ b/src/routes/blog/new/+page.svelte @@ -1,6 +1,9 @@
diff --git a/src/routes/jwt/external/key.pem/+server.ts b/src/routes/jwt/external/key.pem/+server.ts new file mode 100644 index 0000000..ef8c292 --- /dev/null +++ b/src/routes/jwt/external/key.pem/+server.ts @@ -0,0 +1,47 @@ +import { json } from "$lib/server/util/index.js"; + +export function GET({ request }) { + return new Response( + `-----BEGIN CERTIFICATE----- +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== +-----END CERTIFICATE-----`, + { + headers: { + "content-type": "text/plain", + }, + }, + ); +} + +export function POST({ request }) { + return new Response(); +} diff --git a/src/routes/jwt/internal/+server.ts b/src/routes/jwt/internal/+server.ts new file mode 100644 index 0000000..ba144e7 --- /dev/null +++ b/src/routes/jwt/internal/+server.ts @@ -0,0 +1,16 @@ +import { PRIVATE_KEY } from "$lib/server/secrets/secrets.json"; +import { useAuth } from "$lib/util/api/index.js"; +import jwt from "jsonwebtoken"; + +export async function POST({ request }) { + const userKey = `${request.headers.get("x-username")}:${request.headers.get("x-token")}`; + const user = await useAuth(userKey); + const token = jwt.sign({ ...user, apiKey: userKey }, Buffer.from(PRIVATE_KEY), { + algorithm: "RS256", + }); + return new Response(token, { + headers: { + "content-type": "text/html", + }, + }); +} diff --git a/src/routes/lobby/+server.ts b/src/routes/lobby/+server.ts new file mode 100644 index 0000000..dd223c5 --- /dev/null +++ b/src/routes/lobby/+server.ts @@ -0,0 +1,65 @@ +import { Room, RoomManager } from "$lib/server/class/Room"; +import { userRepo } from "$lib/server/repo/index.js"; +import { SuyuUser } from "$lib/server/schema"; +import { PUBLIC_KEY } from "$lib/server/secrets/secrets.json"; +import { json } from "$lib/server/util"; +import { useAuth } from "$lib/util/api/index.js"; +import type { IJwtData } from "$types/auth.js"; +import type { IRoom, LobbyResponse } from "$types/rooms"; +import jwt from "jsonwebtoken"; + +export async function GET({ request }) { + return json({ + rooms: RoomManager.getRooms().map((r) => r.toJSON()), + }); +} + +/* credit to janeberru for showing the shape of this data */ +export async function POST({ request, getClientAddress }) { + // TODO: per-ip room limit + const body: IRoom = await request.json(); + /* description may contain "### END DESCRIPTION ###" on its own line. if it does, get all lines after that */ + const parsedDescription = body.description.split("### END DESCRIPTION ###"); + console.log(parsedDescription); + const description = parsedDescription?.slice(1)?.join("### END DESCRIPTION ###") || ""; + const opts: { [key: string]: string } = {}; + description.split("\n").forEach((line) => { + const [key, ...values] = line.split("="); + const value = values.join("=").trim(); + if (!key || !value) return; + opts[key] = value; + }); + if (opts.ip) { + if ( + !opts.ip.match( + /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, + ) + ) { + return new Response(null, { status: 400 }); + } + } + const token = request.headers.get("authorization"); + if (!token) return new Response(null, { status: 401 }); + // TODO: jwt utils which type and validate automatically + const user = await useAuth(token); + console.log(user); + if (!user) return new Response(null, { status: 401 }); + const room = RoomManager.createRoom({ + name: body.name, + description: parsedDescription[0] || "", + gameName: body.preferredGameName, + gameId: body.preferredGameId, + players: [ + { + gameId: 0, + gameName: "", + nickname: user.username, + }, + ], + maxPlayers: body.maxPlayers, + ip: `${opts.ip || getClientAddress().split(":").at(-1)}:${body.port}`, + host: user, + hasPassword: body.hasPassword || false, + }); + return json(room.toJSON()); +} diff --git a/src/routes/lobby/[id]/+server.ts b/src/routes/lobby/[id]/+server.ts new file mode 100644 index 0000000..b85cea8 --- /dev/null +++ b/src/routes/lobby/[id]/+server.ts @@ -0,0 +1,30 @@ +import { RoomManager } from "$lib/server/class/Room"; +import { json } from "$lib/server/util/index.js"; +import { useAuth } from "$lib/util/api/index.js"; + +/* thanks again janeberru for the shape of this data */ +export async function POST({ request, params }) { + const body = await request.json(); + const { id } = params; + const room = RoomManager.getRoom(id); + if (!room) return new Response(null, { status: 500 }); + const user = await useAuth(request.headers.get("authorization") || ""); + if (!user) return new Response(null, { status: 401 }); + if (user.id !== room.host.id) return new Response(null, { status: 401 }); + if (body.players.length === 0 && room.roomInfo.owner) { + console.log(room.roomInfo.players); + room.setPlayerList([{ gameId: 0, gameName: "", nickname: room.roomInfo.owner }]); + } + return json({ message: "Lobby updated successfully" }); +} + +export async function DELETE({ request, params }) { + const { id } = params; + const room = RoomManager.getRoom(id); + if (!room) return new Response(null, { status: 500 }); + const user = await useAuth(request.headers.get("authorization") || ""); + if (!user) return new Response(null, { status: 401 }); + if (user.id !== room.host.id) return new Response(null, { status: 401 }); + room.delete(); + return json(room.toJSON()); +} diff --git a/src/routes/profile/+server.ts b/src/routes/profile/+server.ts new file mode 100644 index 0000000..a5c82c2 --- /dev/null +++ b/src/routes/profile/+server.ts @@ -0,0 +1,11 @@ +import { json } from "$lib/server/util/index"; +import { useAuth } from "$lib/util/api/index.js"; + +export async function GET({ request }) { + const user = await useAuth(request.headers.get("authorization") || ""); + console.log(user); + if (!user) return new Response(null, { status: 401 }); + return json({ + username: user.username, + }); +}