feat:lineage compat, nessa made more similar

This commit is contained in:
2026-07-24 07:40:58 -04:00
parent a876cee6ec
commit 9e285570e1
32 changed files with 536 additions and 129 deletions

View File

@@ -0,0 +1,65 @@
// Shared REST handler for the Lineage REST shim.
//
// Each route file calls `rest()` with a function that receives a typed tRPC
// caller (scoped to `caller.lineage.*`) and the SolidStart APIEvent. The
// handler maps `TRPCError` codes → HTTP status + `{ message }` body (the
// legacy client parses `result.message` on non-OK responses), and returns
// the procedure's return value as JSON on success.
import type { APIEvent } from "@solidjs/start/server";
import { TRPCError } from "@trpc/server";
import { createServerCaller } from "~/server/api/root";
const codeToStatus: Record<string, number> = {
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
TIMEOUT: 408,
PAYLOAD_TOO_LARGE: 413,
METHOD_NOT_SUPPORTED: 405,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER_ERROR: 500
};
type Caller = Awaited<ReturnType<typeof createServerCaller>>;
export async function rest(
fn: (caller: Caller, event: APIEvent) => Promise<unknown>,
event: APIEvent
): Promise<Response> {
try {
const caller = await createServerCaller(event);
const result = await fn(caller, event);
return new Response(JSON.stringify(result), {
status: 200,
headers: { "content-type": "application/json" }
});
} catch (e) {
if (e instanceof TRPCError) {
const status = codeToStatus[e.code] ?? 500;
return new Response(JSON.stringify({ message: e.message }), {
status,
headers: { "content-type": "application/json" }
});
}
console.error("Lineage REST shim error:", e);
return new Response(JSON.stringify({ message: "Internal server error" }), {
status: 500,
headers: { "content-type": "application/json" }
});
}
}
/** Extract the Bearer token from the Authorization header. */
export function bearerToken(event: APIEvent): string | null {
const auth = event.request.headers.get("authorization") ?? "";
const m = auth.match(/^Bearer\s+(.+)$/i);
return m?.[1]?.trim() ?? null;
}
/** Parse the JSON request body. */
export async function jsonBody<T = any>(event: APIEvent): Promise<T> {
return await event.request.json();
}

View File

@@ -0,0 +1,8 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "./_lib";
export const POST = (event: APIEvent) =>
rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.misc.analytics(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.auth.appleGetEmail(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.auth.appleRegistration(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.database.databaseCreds(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.database.deletionCancel(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.database.deletionCheck(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.database.deletionInit(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.auth.emailLogin(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest, bearerToken } from "../../_lib";
export const GET = (event: APIEvent) => rest(async (caller) => {
const token = bearerToken(event);
return caller.lineage.auth.refreshToken({ token: token ?? "" });
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.auth.refreshVerification(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.auth.emailRegistration(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.auth.emailVerification(input);
}, event);

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.auth.googleRegistration(input);
}, event);

View File

@@ -0,0 +1,6 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const GET = (event: APIEvent) => rest(async (caller) => {
return caller.lineage.jsonService.attacks();
}, event);

View File

@@ -0,0 +1,6 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const GET = (event: APIEvent) => rest(async (caller) => {
return caller.lineage.jsonService.conditions();
}, event);

View File

@@ -0,0 +1,6 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const GET = (event: APIEvent) => rest(async (caller) => {
return caller.lineage.jsonService.dungeons();
}, event);

View File

@@ -0,0 +1,6 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const GET = (event: APIEvent) => rest(async (caller) => {
return caller.lineage.jsonService.enemies();
}, event);

View File

@@ -0,0 +1,6 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const GET = (event: APIEvent) => rest(async (caller) => {
return caller.lineage.jsonService.items();
}, event);

View File

@@ -0,0 +1,6 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const GET = (event: APIEvent) => rest(async (caller) => {
return caller.lineage.jsonService.misc();
}, event);

View File

@@ -0,0 +1,22 @@
// Plain-text route for the Lineage offline secret.
//
// `misc.offlineSecret` is a tRPC `.query()` returning `{ secret: "…" }`. But
// the Lineage client's `IAPStore.fetchOfflineSecret` does
// `await response.text()` and uses the raw string directly as the decryption
// key. trpc-openapi always JSON-encodes responses, so exposing it via the openapi
// shim would yield `'{"secret":"…"}'` as text and break decryption.
//
// This dedicated route returns the secret verbatim as `text/plain`, matching
// exactly what the legacy endpoint returned. Auth: none (the legacy endpoint
// had none either — the secret is a server-side decryption key shared to all
// clients; it rotates with `LINEAGE_OFFLINE_SERIALIZATION_SECRET`).
import type { APIEvent } from "@solidjs/start/server";
import { env } from "~/env/server";
export const GET = (event: APIEvent) => {
return new Response(env.LINEAGE_OFFLINE_SERIALIZATION_SECRET, {
status: 200,
headers: { "content-type": "text/plain; charset=utf-8" }
});
};

View File

@@ -0,0 +1,7 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.pvp.battleResult(input);
}, event);

View File

@@ -0,0 +1,13 @@
import type { APIEvent } from "@solidjs/start/server";
import { rest } from "../_lib";
// GET /api/lineage/pvp — retrieve opponents
export const GET = (event: APIEvent) => rest(async (caller) => {
return caller.lineage.pvp.getOpponents();
}, event);
// POST /api/lineage/pvp — register/update player character
export const POST = (event: APIEvent) => rest(async (caller) => {
const input = await event.request.json();
return caller.lineage.pvp.registerCharacter(input);
}, event);

View File

@@ -54,7 +54,8 @@ export default function NessaLanding() {
const { isDark } = useDarkMode();
const iconSrc = () => (isDark() ? ICON_DARK : ICON_DEFAULT);
const brandColor = () => site().brandColor;
const brandColor = () =>
isDark() ? (site().brandColorDark ?? site().brandColor) : site().brandColor;
return (
<>