117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
import type { XOInvitation } from "@xo-cash/types";
|
|
import { EventEmitter } from "./event-emitter.js";
|
|
import { SSESession, type SSEvent } from "./sse-client.js";
|
|
import { decodeExtendedJson, 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 | undefined> {
|
|
// Send a GET request to the sync server
|
|
const response = await fetch(
|
|
`${this.baseUrl}/invitations?invitationIdentifier=${identifier}`,
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to get invitation: ${response.statusText}`);
|
|
}
|
|
|
|
const invitation = decodeExtendedJson(await response.text()) as
|
|
| XOInvitation
|
|
| undefined;
|
|
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),
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
|
|
// 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 = decodeExtendedJson(await response.text()) as XOInvitation;
|
|
|
|
return data;
|
|
}
|
|
}
|