Compare commits
6 Commits
6ad0ffe1c5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 19e0ae4605 | |||
| 6313364ba4 | |||
| 064528b178 | |||
| f0e522b039 | |||
| daed4245fd | |||
| afea1ce949 |
+55
-4
@@ -15,9 +15,25 @@ type Game = {
|
||||
carts: Parameters<typeof Pico8Player>["0"]["carts"];
|
||||
};
|
||||
|
||||
const getPatch = (before: number[], after: number[]) => {
|
||||
const diff: Record<number, number> = {};
|
||||
for (const i in after) {
|
||||
if (after[i] !== before[i]) {
|
||||
diff[i] = after[i];
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
};
|
||||
|
||||
const applyPatch = (before: number[], patch: Record<number, number>) => {
|
||||
for (const i in patch) {
|
||||
before[i] = patch[i];
|
||||
}
|
||||
};
|
||||
|
||||
export const GamePage = () => {
|
||||
const { author, slug } = useParams();
|
||||
const [text, setText] = useState("");
|
||||
// const [text, setText] = useState("");
|
||||
const [prevGpio, setPrevGpio] = useState<number[] | null>(null);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const room = searchParams.get("room");
|
||||
@@ -46,6 +62,16 @@ export const GamePage = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg.gpioPatch) {
|
||||
if (picoRef.current) {
|
||||
const handle = picoRef.current;
|
||||
if (handle) {
|
||||
// console.log("updating pico gpio");
|
||||
applyPatch(handle.gpio, msg.gpioPatch);
|
||||
setPrevGpio([...handle.gpio]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const [game, setGame] = useState<Game | null>(null);
|
||||
@@ -70,7 +96,9 @@ export const GamePage = () => {
|
||||
if (JSON.stringify(handle.gpio) !== JSON.stringify(prevGpio)) {
|
||||
if (prevGpio) {
|
||||
setPrevGpio([...handle.gpio]);
|
||||
socket.sendMessage({ gpio: handle.gpio });
|
||||
socket.sendMessage({
|
||||
gpioPatch: getPatch(prevGpio, handle.gpio),
|
||||
});
|
||||
} else {
|
||||
socket.sendMessage({ getGpio: true });
|
||||
setPrevGpio([...handle.gpio]);
|
||||
@@ -127,7 +155,30 @@ export const GamePage = () => {
|
||||
<Pico8Player consoleRef={picoRef} carts={game.carts} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`}
|
||||
>
|
||||
{game.carts.map((cart) =>
|
||||
"src" in cart ? (
|
||||
<div
|
||||
key={cart.name}
|
||||
className={css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`}
|
||||
>
|
||||
<img src={cart.src} />
|
||||
<a href={cart.src} download={`${cart.name}.p8.png`}>
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
{/* <div>
|
||||
<input onChange={(x) => setText(x.target.value)} />
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -136,7 +187,7 @@ export const GamePage = () => {
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* <div>
|
||||
<p>This is a paragraph about this game. It is a cool game. And a cool website to play it on. It automagically connects from GitHub.</p>
|
||||
</div> */}
|
||||
|
||||
@@ -20,7 +20,7 @@ const handler = async ({ payload }: FirRouteInput<typeof payloadT>) => {
|
||||
games: [],
|
||||
};
|
||||
}
|
||||
console.log("author", authorData);
|
||||
// console.log("author", authorData);
|
||||
|
||||
const pat = decrypt({
|
||||
password: process.env.ENCRYPTION_PASSWORD!,
|
||||
@@ -47,7 +47,6 @@ const handler = async ({ payload }: FirRouteInput<typeof payloadT>) => {
|
||||
.filter((x) => x.endsWith(".p8.png"))
|
||||
.map((x) => x.slice(0, -".p8.png".length)),
|
||||
);
|
||||
console.log("hi");
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -17,7 +17,7 @@ const handler = async ({ payload }: FirRouteInput<typeof payloadT>) => {
|
||||
if (!authorData) {
|
||||
return null;
|
||||
}
|
||||
console.log("author", authorData);
|
||||
// console.log("author", authorData);
|
||||
|
||||
const pat = decrypt({
|
||||
password: process.env.ENCRYPTION_PASSWORD!,
|
||||
@@ -36,7 +36,7 @@ const handler = async ({ payload }: FirRouteInput<typeof payloadT>) => {
|
||||
})
|
||||
).data;
|
||||
|
||||
console.log(gameData);
|
||||
// console.log(gameData);
|
||||
|
||||
if (Array.isArray(gameData)) {
|
||||
return null;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { FirRouteInput, FirRouteOptions } from "../util/routewrap.js";
|
||||
|
||||
const method = "POST";
|
||||
const url = "/api/webhook-gh";
|
||||
|
||||
const payloadT = Type.Any();
|
||||
|
||||
const handler = ({payload}: FirRouteInput<typeof payloadT>) => {
|
||||
console.log(payload);
|
||||
return {};
|
||||
};
|
||||
|
||||
export default {
|
||||
method,
|
||||
url,
|
||||
payloadT,
|
||||
handler,
|
||||
} as const satisfies FirRouteOptions<typeof payloadT>;
|
||||
+1
-1
@@ -9,7 +9,7 @@ const server = Fastify({
|
||||
logger: true,
|
||||
});
|
||||
|
||||
server.register(fastifyWebsocket);
|
||||
server.register<any>(fastifyWebsocket, { server: server.server });
|
||||
|
||||
server.register(fastifyStatic, {
|
||||
root: new URL("public", import.meta.url).toString().slice("file://".length),
|
||||
|
||||
@@ -2,8 +2,7 @@ import echo from "./api/echo.ts";
|
||||
import getAuthor from "./api/getAuthor.ts";
|
||||
import getGame from "./api/getGame.ts";
|
||||
import room from "./api/room.ts";
|
||||
import webhook from "./api/webhook.ts";
|
||||
|
||||
export const routeList = [echo, webhook, getAuthor, room, getGame];
|
||||
export const routeList = [echo, getAuthor, room, getGame];
|
||||
|
||||
export type RouteList = typeof routeList;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Static, TSchema } from "@sinclair/typebox";
|
||||
import { Value } from "@sinclair/typebox/value";
|
||||
import { FastifyInstance, FastifyRequest, HTTPMethods } from "fastify"
|
||||
import { FastifyInstance, FastifyRequest, HTTPMethods } from "fastify";
|
||||
import { RouteOptions } from "fastify/types/route.js";
|
||||
import { type WebSocket } from "@fastify/websocket";
|
||||
|
||||
@@ -9,113 +9,129 @@ type WebsocketConnection = Parameters<Defined<RouteOptions["wsHandler"]>>[0];
|
||||
type URLString = string;
|
||||
|
||||
export type FirRouteInput<TPayloadSchema extends TSchema> = {
|
||||
payload: Static<TPayloadSchema>,
|
||||
}
|
||||
|
||||
export type FirWebsocketInput<TPayloadSchema extends TSchema> = {
|
||||
socket: WebsocketConnection,
|
||||
req: FastifyRequest,
|
||||
payload: Static<TPayloadSchema>,
|
||||
}
|
||||
|
||||
export type FirWebsocketHandler<TIn extends TSchema = TSchema> = {
|
||||
onMessage?(input: FirWebsocketInput<TIn>): void,
|
||||
onOpen?(input: {socket: WebSocket, req: FastifyRequest}): void,
|
||||
onClose?(input: {socket: WebSocket, req: FastifyRequest}): void,
|
||||
onError?(input: {socket: WebSocket, req: FastifyRequest, error: unknown}): void,
|
||||
payload: Static<TPayloadSchema>;
|
||||
};
|
||||
|
||||
export type FirRouteOptions<TIn extends TSchema = TSchema, TOut extends TSchema = TSchema> = {
|
||||
method: HTTPMethods,
|
||||
url: URLString,
|
||||
payloadT: TIn,
|
||||
responseT?: TOut,
|
||||
} & ({
|
||||
handler: (input: FirRouteInput<TIn>) => Static<TOut> | Promise<Static<TOut>>,
|
||||
} | {
|
||||
websocket: FirWebsocketHandler<TIn>,
|
||||
})
|
||||
export type FirWebsocketInput<TPayloadSchema extends TSchema> = {
|
||||
socket: WebsocketConnection;
|
||||
req: FastifyRequest;
|
||||
payload: Static<TPayloadSchema>;
|
||||
};
|
||||
|
||||
export type FirWebsocketHandler<TIn extends TSchema = TSchema> = {
|
||||
onMessage?(input: FirWebsocketInput<TIn>): void;
|
||||
onOpen?(input: { socket: WebSocket; req: FastifyRequest }): void;
|
||||
onClose?(input: { socket: WebSocket; req: FastifyRequest }): void;
|
||||
onError?(input: {
|
||||
socket: WebSocket;
|
||||
req: FastifyRequest;
|
||||
error: unknown;
|
||||
}): void;
|
||||
};
|
||||
|
||||
export type FirRouteOptions<
|
||||
TIn extends TSchema = TSchema,
|
||||
TOut extends TSchema = TSchema,
|
||||
> = {
|
||||
method: HTTPMethods;
|
||||
url: URLString;
|
||||
payloadT: TIn;
|
||||
responseT?: TOut;
|
||||
} & (
|
||||
| {
|
||||
handler: (
|
||||
input: FirRouteInput<TIn>,
|
||||
) => Static<TOut> | Promise<Static<TOut>>;
|
||||
}
|
||||
| {
|
||||
websocket: FirWebsocketHandler<TIn>;
|
||||
}
|
||||
);
|
||||
|
||||
type Defined<T> = T extends undefined ? never : T;
|
||||
|
||||
export const attachRoute = <TIn extends TSchema, TOut extends TSchema>(server: FastifyInstance, routeOptions: FirRouteOptions<TIn, TOut>) => {
|
||||
const {
|
||||
method,
|
||||
url,
|
||||
payloadT,
|
||||
} = routeOptions;
|
||||
export const attachRoute = <TIn extends TSchema, TOut extends TSchema>(
|
||||
server: FastifyInstance,
|
||||
routeOptions: FirRouteOptions<TIn, TOut>,
|
||||
) => {
|
||||
const { method, url, payloadT } = routeOptions;
|
||||
|
||||
if ("websocket" in routeOptions) {
|
||||
console.log('SETTING UP WS');
|
||||
const {websocket} = routeOptions;
|
||||
server.register(async function(fastify: FastifyInstance) {
|
||||
fastify.get('/api/ws/room', { websocket: true }, (socket: WebSocket, req: FastifyRequest) => {
|
||||
websocket.onOpen && websocket.onOpen({socket, req});
|
||||
socket.on('message', (message: any) => {
|
||||
const payload = JSON.parse(message.toString());
|
||||
if (Value.Check(payloadT, payload)) {
|
||||
websocket.onMessage && websocket.onMessage({socket, payload, req});
|
||||
} else {
|
||||
throw new Error("Payload wrong shape.");
|
||||
}
|
||||
});
|
||||
socket.on('close', () => {
|
||||
websocket.onClose && websocket.onClose({socket, req});
|
||||
});
|
||||
socket.on('error', (error: any) => {
|
||||
websocket.onError && websocket.onError({socket, error, req});
|
||||
});
|
||||
})
|
||||
console.log("SETTING UP WS");
|
||||
const { websocket } = routeOptions;
|
||||
server.register(async function (fastify: FastifyInstance) {
|
||||
fastify.get(
|
||||
"/api/ws/room",
|
||||
{ websocket: true },
|
||||
(socket: WebSocket, req: FastifyRequest) => {
|
||||
websocket.onOpen && websocket.onOpen({ socket, req });
|
||||
socket.on("message", (message: any) => {
|
||||
const payload = JSON.parse(message.toString());
|
||||
if (Value.Check(payloadT, payload)) {
|
||||
websocket.onMessage &&
|
||||
websocket.onMessage({ socket, payload, req });
|
||||
} else {
|
||||
throw new Error("Payload wrong shape.");
|
||||
}
|
||||
});
|
||||
socket.on("close", () => {
|
||||
websocket.onClose && websocket.onClose({ socket, req });
|
||||
});
|
||||
socket.on("error", (error: any) => {
|
||||
websocket.onError &&
|
||||
websocket.onError({ socket, error, req });
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
return;
|
||||
|
||||
// const {websocket} = routeOptions;
|
||||
// const augmentedWsHandler = (conn: Parameters<Defined<RouteOptions["wsHandler"]>>[0]) => {
|
||||
// console.log('HELLO');
|
||||
// conn.on("message", (message) => {
|
||||
// const payload = JSON.parse(message.toString());
|
||||
// if (Value.Check(payloadT, payload)) {
|
||||
// websocket({socket: conn, payload});
|
||||
// } else {
|
||||
// throw new Error("Payload wrong shape.");
|
||||
// }
|
||||
// });
|
||||
// console.log('HELLO');
|
||||
// conn.on("message", (message) => {
|
||||
// const payload = JSON.parse(message.toString());
|
||||
// if (Value.Check(payloadT, payload)) {
|
||||
// websocket({socket: conn, payload});
|
||||
// } else {
|
||||
// throw new Error("Payload wrong shape.");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// return {
|
||||
// method: 'GET', // WebSocket upgrades are GET requests
|
||||
// url,
|
||||
// method: 'GET', // WebSocket upgrades are GET requests
|
||||
// url,
|
||||
// // websocket: true,
|
||||
// wsHandler: augmentedWsHandler,
|
||||
// wsHandler: augmentedWsHandler,
|
||||
// handler: (...args) => {
|
||||
// console.log('socket!');
|
||||
// const socket = args[0].socket.on("message", () => {
|
||||
// console.log("connected!");
|
||||
// })
|
||||
// },
|
||||
// // handler: (request, reply) => {
|
||||
// // reply.code(405).send({ message: 'Method Not Allowed' }); // Handle non-WebSocket requests
|
||||
// // }
|
||||
// }
|
||||
// // handler: (request, reply) => {
|
||||
// // reply.code(405).send({ message: 'Method Not Allowed' }); // Handle non-WebSocket requests
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
|
||||
const {handler} = routeOptions;
|
||||
const augmentedHandler = (request: Parameters<RouteOptions["handler"]>[0]) => {
|
||||
const {
|
||||
body,
|
||||
query,
|
||||
} = request;
|
||||
const { handler } = routeOptions;
|
||||
const augmentedHandler = (
|
||||
request: Parameters<RouteOptions["handler"]>[0],
|
||||
) => {
|
||||
const { body, query } = request;
|
||||
const payload = body ?? query;
|
||||
if (Value.Check(payloadT, payload)) {
|
||||
return handler({payload});
|
||||
return handler({ payload });
|
||||
} else {
|
||||
throw new Error("Payload wrong shape.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
server.route({
|
||||
method,
|
||||
url,
|
||||
handler: augmentedHandler,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user