Improve router encoding

This commit is contained in:
2026-05-22 12:12:13 +00:00
parent 85dc121333
commit bd1431f3ba
4 changed files with 44 additions and 25 deletions

View File

@@ -5,6 +5,7 @@ import type { StoreSQLite } from '../services/invitation-store.js';
import { parseInvitation } from '../utils/invitation-parser.js';
import Z from 'zod';
import { encodeExtendedJson } from '../utils/ext-json.js';
export class InvitationsRoute {
constructor(
@@ -27,6 +28,12 @@ export class InvitationsRoute {
];
}
/**
* Get an invitation, and if the text/event-stream header is present, subscribe the client to the SSE stream.
* @param request - The request.
* @param reply - The reply.
* @returns The invitation.
*/
async getInvitation(request: FastifyRequest, reply: FastifyReply) {
// Get the invitation identifier from the query
const { invitationIdentifier } = request.query as { invitationIdentifier?: string };
@@ -39,30 +46,29 @@ export class InvitationsRoute {
// Get the invitation from the store
const storedInvitation = await this.invitationStore.get(invitationIdentifier);
if (request.headers['accept'] === 'text/event-stream') {
// Subscribe the client to the SSE stream.
await this.sseBroadcaster.subscribe(request, reply);
// If the client is not subscribing to the SSE stream, return the invitation.
if (request.headers['accept'] !== 'text/event-stream') {
return encodeExtendedJson(storedInvitation || {});
}
// If the invitation doesn't exist, don't send anything.
if (!storedInvitation) {
return;
}
// Its an SSE request, so we need to subscribe the client to the SSE stream.
await this.sseBroadcaster.subscribe(request, reply);
// Send the invitation to the client as if it was a get request.
this.sseBroadcaster.sendEvent(reply, 'invitation-updated', storedInvitation);
// Return early
// If the invitation doesn't exist, don't send anything.
if (!storedInvitation) {
return;
}
if(!storedInvitation) {
return reply.status(200).send({});
}
// If the client is not subscribing to the SSE stream, return the invitation.
return reply.status(200).send(storedInvitation);
// Send the invitation to the client as if it was a get request.
this.sseBroadcaster.sendEvent(reply, 'invitation-updated', storedInvitation);
}
/**
* Update the invitation.
* @param request - The request.
* @param reply - The reply.
* @returns The merged invitation.
*/
async updateInvitation(request: FastifyRequest, reply: FastifyReply) {
// Parse the invitation
const invitation = parseInvitation.parse(request.body);
@@ -79,7 +85,7 @@ export class InvitationsRoute {
// Broadcast the invitation update (We send down the whole invitation. Clients will have to compare commitIds)
await this.sseBroadcaster.broadcast(invitation.invitationIdentifier, 'invitation-updated', invitation);
return reply.status(200).send(invitation);
return invitation;
}
/**

View File

@@ -5,7 +5,7 @@ import { z } from "zod";
import {
decodeExtendedJsonObject,
encodeExtendedJsonObject,
} from "../utils/ext-json";
} from "../utils/ext-json.js";
// Interface to add to our route classes so that we can register them.
// NOTE: I hate this pattern. But ExpressJS is odd in that it is structured as a singleton that still needs registration.

View File

@@ -1,5 +1,7 @@
import sqlite3, { type Database } from 'better-sqlite3';
import { pack, unpack } from 'msgpackr';
import { encodeExtendedJsonObject, decodeExtendedJsonObject } from '../utils/ext-json.js';
import { binToHex, hexToBin } from '@bitauth/libauth';
export interface SQLiteOptions {
wal: boolean;
@@ -107,25 +109,34 @@ export class StoreSQLite<T> {
async get(key: string): Promise<T | undefined> {
const result = this.db
.prepare(`SELECT value FROM "${this.storeName}" WHERE key = ?`)
.get(key) as { value: Buffer } | undefined;
.get(key) as { value: string } | undefined;
if (!result) {
return undefined;
}
// Deserialize using msgpackr for consistency with other implementations
return unpack(result.value) as T;
const binValue = hexToBin(result.value);
const unpackedValue = unpack(binValue);
const decodedValue = decodeExtendedJsonObject(unpackedValue);
return decodedValue as T;
}
async set(key: string, value: T): Promise<void> {
const encodedValue = encodeExtendedJsonObject(value);
// Serialize using msgpackr for consistency with other implementations
const serializedValue = pack(value);
const packedValue = pack(encodedValue);
const serializedValue = binToHex(packedValue);
this.db
.prepare(
`INSERT OR REPLACE INTO "${this.storeName}" (key, value) VALUES (?, ?)`,
)
.run(key, Buffer.from(serializedValue));
.run(key, serializedValue);
}
async delete(key: string): Promise<void> {

View File

@@ -2,6 +2,8 @@ import type { FastifyReply, FastifyRequest } from "fastify";
import debug, { type Debugger } from "debug";
import { encodeExtendedJson } from "../utils/ext-json.js";
/**
* Represents an event stored in the history buffer.
* Used for replaying missed events to reconnecting clients.
@@ -87,7 +89,7 @@ export class SSEBroadcaster {
const timestamp = Date.now();
client.raw.write(`id: ${timestamp}\n`);
client.raw.write(`event: ${topic}\n`);
client.raw.write(`data: ${JSON.stringify(data)}\n\n`);
client.raw.write(`data: ${encodeExtendedJson(data)}\n\n`);
}
/**