-
- 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 @@
+({
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,
+ });
+}