···11-import type { Account } from "../Account";
11+import type { Account } from "cartography-api/account";
2233export class AuthEvent extends Event {
44 account: Account;
+7-50
src/lib/appserver/socket/Message.ts
···11/* eslint-disable @typescript-eslint/naming-convention -- this is a server owned field */
2233-import type { Account } from "../Account";
44-import type { Field } from "../Field";
55-import type { FieldTile } from "../FieldTile";
66-import type { Card } from "../Card";
33+import type { Authenticate, DebugAddCard } from "cartography-api/request";
44+import type { Authenticated, PatchData } from "cartography-api/response";
7588-export interface MessageReplyMap {
99- auth: AccountMessage;
1010- get_fields: FieldsMessage;
1111- get_field: FieldMessage;
1212- subscribe: never;
1313- unsubscribe: never;
1414-}
1515-1616-export interface AnyMessage {
1717- type: string;
1818- id: string;
1919- data: unknown;
2020-}
2121-2222-export interface AccountMessage extends AnyMessage {
2323- type: "account";
2424- data: { account: Account };
2525-}
2626-2727-export interface FieldMessage extends AnyMessage {
2828- type: "field";
2929- data: { field: Field; field_tiles: FieldTile[] };
3030-}
3131-3232-export interface FieldsMessage extends AnyMessage {
3333- type: "field";
3434- data: { fields: Field[] };
3535-}
3636-3737-export interface FieldTileMessage extends AnyMessage {
3838- type: "field_tile";
3939-4040- data: { field_tile: FieldTile };
4141-}
4242-4343-export interface CardMessage extends AnyMessage {
4444- type: "card";
4545- data: { card: Card };
4646-}
4747-4848-export type Message =
4949- | AccountMessage
5050- | FieldMessage
5151- | CardMessage
5252- | FieldTileMessage
5353- | FieldsMessage;
66+export type MessageReply<T> = T extends Authenticate
77+ ? Authenticated
88+ : T extends DebugAddCard
99+ ? PatchData
1010+ : void;
+3-3
src/lib/appserver/socket/MessageEvent.ts
···11-import type { Message } from "./Message";
11+import type { Message$ } from "cartography-api/response";
2233export class MessageEvent extends Event {
44- message: Message;
44+ message: Message$;
5566- constructor(message: Message) {
66+ constructor(message: Message$) {
77 super("message");
88 this.message = message;
99 }
+6-6
src/lib/appserver/socket/OneOff.svelte.ts
···11-import type { MessageReplyMap } from "./Message";
11+import * as Response from "cartography-api/response";
22import type { MessageEvent } from "./MessageEvent";
33import { type SocketV1 } from "./SocketV1";
4455-export class OneOff<T extends keyof MessageReplyMap> extends EventTarget {
55+export class OneOff<T> extends EventTarget {
66 #socket: SocketV1;
77 id: string;
88···1313 }
14141515 async reply(abort?: AbortSignal) {
1616- return new Promise<MessageReplyMap[T]>((resolve, reject) => {
1616+ return new Promise<T>((resolve, reject) => {
1717 abort?.throwIfAborted();
18181919 const handler = (event: MessageEvent) => {
2020- if (event.message.id === this.id) {
2121- resolve(event.message as MessageReplyMap[T]);
2020+ if (Response.id(event.message) === this.id) {
2121+ resolve(Response.response(event.message) as T);
2222 this.#socket.removeEventListener("message", handler);
2323 abort?.removeEventListener("abort", onabort);
2424 }
···3434 });
3535 }
36363737- $then(callback: (event: MessageReplyMap[T]) => void): void {
3737+ $then(callback: (event: T) => void): void {
3838 $effect(() => {
3939 const abort = new AbortController();
4040
+13-32
src/lib/appserver/socket/SocketV1.ts
···22import { AuthEvent } from "./AuthEvent";
33import { OneOff } from "./OneOff.svelte";
44import { Subscription, type Channel } from "./Subscription";
55-import type { Message, MessageReplyMap } from "./Message";
55+import type { MessageReply } from "./Message";
66import { ReactiveEventTarget } from "$lib/ReactiveEventTarget.svelte";
77import type { FieldId } from "../Field";
88+import { Result$isOk, Result$Ok$0 } from "cartography-api/prelude";
99+import * as Request from "cartography-api/request";
1010+import * as Response from "cartography-api/response";
811912interface SocketV1EventMap {
1013 message: MessageEvent;
···4043 this.#socket.close(1003, "Only text messages are supported");
4144 this.dispatchEvent(new Event("error"));
4245 }
4343- try {
4444- const message = JSON.parse(data) as Message;
4545- this.dispatchEvent(new MessageEvent(message));
4646- } catch {
4646+ const message = Response.from_string(data);
4747+ if (Result$isOk(message)) {
4848+ this.dispatchEvent(new MessageEvent(Result$Ok$0(message)!));
4949+ } else {
4750 this.#socket.close(4000, "Invalid JSON received");
4851 this.dispatchEvent(new Event("error"));
4952 }
···7477 this.#socket.addEventListener("close", onClose);
7578 }
76797777- #sendMessage<T extends keyof MessageReplyMap>(
7878- type: T,
7979- data: unknown = {},
8080- id: string = window.crypto.randomUUID(),
8181- ) {
8282- this.#socket.send(JSON.stringify({ type, data, id }));
8383- return new OneOff<T>(this, id);
8080+ #sendMessage<T extends Request.Request$>(request: T, id: string = window.crypto.randomUUID()) {
8181+ this.#socket.send(Request.to_string(Request.message(request, id)));
8282+ return new OneOff<MessageReply<T>>(this, id);
8483 }
85848685 auth(data: { id: string }) {
8787- this.#sendMessage("auth", data)
8686+ this.#sendMessage<Request.Authenticate>(Request.authenticate(data.id) as Request.Authenticate)
8887 .reply()
8988 .then((event) => {
9090- this.dispatchEvent(new AuthEvent(event.data.account));
8989+ this.dispatchEvent(new AuthEvent(event[0]));
9190 });
9292- }
9393-9494- getFields() {
9595- return this.#sendMessage("get_fields");
9696- }
9797-9898- getField(id: FieldId) {
9999- return this.#sendMessage("get_field", { field_id: id });
100100- }
101101-102102- unsubscribe(id: string) {
103103- this.#sendMessage("unsubscribe", {}, id);
104104- }
105105-106106- subscribe<C extends Channel>(channel: C) {
107107- const id = window.crypto.randomUUID();
108108- this.#sendMessage("subscribe", { channel }, id);
109109- return new Subscription<C>(this, id);
11091 }
1119211293 close(code?: number, reason?: string) {