Improve router encoding
This commit is contained in:
@@ -5,6 +5,7 @@ import type { StoreSQLite } from '../services/invitation-store.js';
|
|||||||
import { parseInvitation } from '../utils/invitation-parser.js';
|
import { parseInvitation } from '../utils/invitation-parser.js';
|
||||||
|
|
||||||
import Z from 'zod';
|
import Z from 'zod';
|
||||||
|
import { encodeExtendedJson } from '../utils/ext-json.js';
|
||||||
|
|
||||||
export class InvitationsRoute {
|
export class InvitationsRoute {
|
||||||
constructor(
|
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) {
|
async getInvitation(request: FastifyRequest, reply: FastifyReply) {
|
||||||
// Get the invitation identifier from the query
|
// Get the invitation identifier from the query
|
||||||
const { invitationIdentifier } = request.query as { invitationIdentifier?: string };
|
const { invitationIdentifier } = request.query as { invitationIdentifier?: string };
|
||||||
@@ -39,30 +46,29 @@ export class InvitationsRoute {
|
|||||||
// Get the invitation from the store
|
// Get the invitation from the store
|
||||||
const storedInvitation = await this.invitationStore.get(invitationIdentifier);
|
const storedInvitation = await this.invitationStore.get(invitationIdentifier);
|
||||||
|
|
||||||
if (request.headers['accept'] === 'text/event-stream') {
|
// If the client is not subscribing to the SSE stream, return the invitation.
|
||||||
// Subscribe the client to the SSE stream.
|
if (request.headers['accept'] !== 'text/event-stream') {
|
||||||
await this.sseBroadcaster.subscribe(request, reply);
|
return encodeExtendedJson(storedInvitation || {});
|
||||||
|
}
|
||||||
|
|
||||||
// If the invitation doesn't exist, don't send anything.
|
// Its an SSE request, so we need to subscribe the client to the SSE stream.
|
||||||
if (!storedInvitation) {
|
await this.sseBroadcaster.subscribe(request, reply);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the invitation to the client as if it was a get request.
|
// If the invitation doesn't exist, don't send anything.
|
||||||
this.sseBroadcaster.sendEvent(reply, 'invitation-updated', storedInvitation);
|
if (!storedInvitation) {
|
||||||
|
|
||||||
// Return early
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!storedInvitation) {
|
// Send the invitation to the client as if it was a get request.
|
||||||
return reply.status(200).send({});
|
this.sseBroadcaster.sendEvent(reply, 'invitation-updated', storedInvitation);
|
||||||
}
|
|
||||||
|
|
||||||
// If the client is not subscribing to the SSE stream, return the invitation.
|
|
||||||
return reply.status(200).send(storedInvitation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the invitation.
|
||||||
|
* @param request - The request.
|
||||||
|
* @param reply - The reply.
|
||||||
|
* @returns The merged invitation.
|
||||||
|
*/
|
||||||
async updateInvitation(request: FastifyRequest, reply: FastifyReply) {
|
async updateInvitation(request: FastifyRequest, reply: FastifyReply) {
|
||||||
// Parse the invitation
|
// Parse the invitation
|
||||||
const invitation = parseInvitation.parse(request.body);
|
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)
|
// 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);
|
await this.sseBroadcaster.broadcast(invitation.invitationIdentifier, 'invitation-updated', invitation);
|
||||||
|
|
||||||
return reply.status(200).send(invitation);
|
return invitation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|||||||
import {
|
import {
|
||||||
decodeExtendedJsonObject,
|
decodeExtendedJsonObject,
|
||||||
encodeExtendedJsonObject,
|
encodeExtendedJsonObject,
|
||||||
} from "../utils/ext-json";
|
} from "../utils/ext-json.js";
|
||||||
|
|
||||||
// Interface to add to our route classes so that we can register them.
|
// 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.
|
// NOTE: I hate this pattern. But ExpressJS is odd in that it is structured as a singleton that still needs registration.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import sqlite3, { type Database } from 'better-sqlite3';
|
import sqlite3, { type Database } from 'better-sqlite3';
|
||||||
import { pack, unpack } from 'msgpackr';
|
import { pack, unpack } from 'msgpackr';
|
||||||
|
import { encodeExtendedJsonObject, decodeExtendedJsonObject } from '../utils/ext-json.js';
|
||||||
|
import { binToHex, hexToBin } from '@bitauth/libauth';
|
||||||
|
|
||||||
export interface SQLiteOptions {
|
export interface SQLiteOptions {
|
||||||
wal: boolean;
|
wal: boolean;
|
||||||
@@ -107,25 +109,34 @@ export class StoreSQLite<T> {
|
|||||||
async get(key: string): Promise<T | undefined> {
|
async get(key: string): Promise<T | undefined> {
|
||||||
const result = this.db
|
const result = this.db
|
||||||
.prepare(`SELECT value FROM "${this.storeName}" WHERE key = ?`)
|
.prepare(`SELECT value FROM "${this.storeName}" WHERE key = ?`)
|
||||||
.get(key) as { value: Buffer } | undefined;
|
.get(key) as { value: string } | undefined;
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize using msgpackr for consistency with other implementations
|
const binValue = hexToBin(result.value);
|
||||||
return unpack(result.value) as T;
|
|
||||||
|
const unpackedValue = unpack(binValue);
|
||||||
|
|
||||||
|
const decodedValue = decodeExtendedJsonObject(unpackedValue);
|
||||||
|
|
||||||
|
return decodedValue as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
async set(key: string, value: T): Promise<void> {
|
async set(key: string, value: T): Promise<void> {
|
||||||
|
const encodedValue = encodeExtendedJsonObject(value);
|
||||||
|
|
||||||
// Serialize using msgpackr for consistency with other implementations
|
// Serialize using msgpackr for consistency with other implementations
|
||||||
const serializedValue = pack(value);
|
const packedValue = pack(encodedValue);
|
||||||
|
|
||||||
|
const serializedValue = binToHex(packedValue);
|
||||||
|
|
||||||
this.db
|
this.db
|
||||||
.prepare(
|
.prepare(
|
||||||
`INSERT OR REPLACE INTO "${this.storeName}" (key, value) VALUES (?, ?)`,
|
`INSERT OR REPLACE INTO "${this.storeName}" (key, value) VALUES (?, ?)`,
|
||||||
)
|
)
|
||||||
.run(key, Buffer.from(serializedValue));
|
.run(key, serializedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(key: string): Promise<void> {
|
async delete(key: string): Promise<void> {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import type { FastifyReply, FastifyRequest } from "fastify";
|
|||||||
|
|
||||||
import debug, { type Debugger } from "debug";
|
import debug, { type Debugger } from "debug";
|
||||||
|
|
||||||
|
import { encodeExtendedJson } from "../utils/ext-json.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an event stored in the history buffer.
|
* Represents an event stored in the history buffer.
|
||||||
* Used for replaying missed events to reconnecting clients.
|
* Used for replaying missed events to reconnecting clients.
|
||||||
@@ -87,7 +89,7 @@ export class SSEBroadcaster {
|
|||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
client.raw.write(`id: ${timestamp}\n`);
|
client.raw.write(`id: ${timestamp}\n`);
|
||||||
client.raw.write(`event: ${topic}\n`);
|
client.raw.write(`event: ${topic}\n`);
|
||||||
client.raw.write(`data: ${JSON.stringify(data)}\n\n`);
|
client.raw.write(`data: ${encodeExtendedJson(data)}\n\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user