Mid-rewrite

This commit is contained in:
2026-02-06 13:14:24 +00:00
parent 601c3db9d0
commit eb1bf9020e
12 changed files with 1300 additions and 103 deletions

91
src/utils/sync-server.ts Normal file
View File

@@ -0,0 +1,91 @@
import type { XOInvitation } from "@xo-cash/types";
import { EventEmitter } from "./event-emitter.js";
import { SSESession, type SSEvent } from "./sse-client.js";
import { decodeExtendedJsonObject, encodeExtendedJson } from "./ext-json.js";
export type SyncServerEventMap = {
'connected': void;
'disconnected': void;
'error': Error;
'message': SSEvent;
}
export class SyncServer extends EventEmitter<SyncServerEventMap> {
static async from(baseUrl: string, invitationIdentifier: string): Promise<SyncServer> {
const server = new SyncServer(baseUrl, invitationIdentifier);
await server.connect();
return server;
}
private sse: SSESession;
constructor(private readonly baseUrl: string, private readonly invitationIdentifier: string) {
super();
// Create an SSE Session
this.sse = new SSESession(`${baseUrl}/invitations?invitationIdentifier=${invitationIdentifier}`, {
method: 'GET',
headers: {
'Accept': 'text/event-stream',
},
// Create our event bubblers
onMessage: (event: SSEvent) => this.emit('message', event),
onError: (error: unknown) => this.emit('error', error instanceof Error ? error : new Error(String(error))),
onDisconnected: () => this.emit('disconnected', undefined),
onConnected: () => this.emit('connected', undefined),
});
}
/**
* Connect to the sync server.
*/
async connect(): Promise<void> {
// Connect to the SSE Session
await this.sse.connect();
}
/**
* Disconnect from the sync server.
*/
async disconnect(): Promise<void> {
// Disconnect from the SSE Session
this.sse.close();
}
/**
* Get the invitation by identifier.
* @param identifier - The invitation identifier.
* @returns The invitation.
*/
async getInvitation(identifier: string): Promise<XOInvitation> {
// Send a GET request to the sync server
const response = await fetch(`${this.baseUrl}/invitations/${identifier}`);
const invitation = await response.json() as XOInvitation;
return invitation;
}
/**
* Publish an invitation.
* @param invitation - The invitation to create.
* @returns The invitation.
*/
async publishInvitation(invitation: XOInvitation): Promise<XOInvitation> {
// Send a POST request to the sync server
const response = await fetch(`${this.baseUrl}/invitations`, {
method: 'POST',
body: encodeExtendedJson(invitation),
});
// Throw is there was an issue with the request
if (!response.ok) {
throw new Error(`Failed to publish invitation: ${response.statusText}`);
}
// Read the returned JSON
// TODO: This should use zod to verify the response
const data = decodeExtendedJsonObject(await response.text()) as XOInvitation;
return data;
}
}