[Fix] Connections

This commit is contained in:
Alex Emmet 2026-08-30 18:26:32 +02:00
commit 74fb46990e
No known key found for this signature in database
14 changed files with 950 additions and 507 deletions

View file

@ -1,6 +1,11 @@
import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";
import { toast as sonnerToast } from "@methanium/ui";
import { base64ToBytes, ConnectionState, MTPClient } from "mtp";
import {
base64ToBytes,
ConnectionState,
MTPClient,
MTPProtocolError,
} from "mtp";
import createAsyncQueue from "@tensamin/shared/asyncQueue";
import {
mtp as mtpSchemas,
@ -17,6 +22,8 @@ import {
type MTPContextType,
type ProtocolMessage,
removeMissingContacts,
sealedRelayResultFromFrame,
type SealedRelaySend,
useMessageHandlers,
} from "./mtpContext";
import {
@ -201,6 +208,23 @@ export function BrowserProvider(props: {
},
[],
);
const sendSealedRelay: SealedRelaySend = useMemo(
() => async (type, data, options) => {
const client = clientRef.current;
if (!client) throw new Error("mtp is not connected");
try {
return sealedRelayResultFromFrame(
await client.requestSealedRelay(type, data, options),
);
} catch (error) {
if (error instanceof MTPProtocolError) {
return sealedRelayResultFromFrame(error.frame);
}
throw error;
}
},
[],
);
const resolveConnectionRef = useRef(() => {});
useEffect(() => {
@ -338,6 +362,7 @@ export function BrowserProvider(props: {
hostPublicKey: { value: omikronPublicKey, encoding: "base64" },
descriptor: "client",
pings: true,
securityProfile: { protectedSignatureSuite: "dual" },
logger: (event) => {
if (event.type === "state") {
if (generation !== connectionGeneration) return;
@ -512,6 +537,7 @@ export function BrowserProvider(props: {
<MTPContext.Provider
value={{
send: sendQueued,
sendSealedRelay,
subscribe,
addInterceptor,
readyState,

View file

@ -1,7 +1,12 @@
export { Provider, useMTP } from "./context";
export { RelayRejectedError, requireRelaySuccess } from "./mtpContext";
export type {
BoundSendFn,
MTPExchange,
MTPInterceptor,
ProtocolMessage,
RelayTarget,
SealedRelayOptions,
SealedRelayResult,
SealedRelaySend,
} from "./mtpContext";

View file

@ -1,5 +1,8 @@
import { createContext, useCallback, useRef } from "react";
import type {
MTPDataValueInput,
MTPEncodedBytesInput,
MTPFrame,
MTPRequestFunction,
MTPResponseFrame,
MTPSubscriptionFunction,
@ -19,6 +22,56 @@ export type ProtocolMessage<
export type BoundSendFn = MTPRequestFunction<typeof mtpSchemas>;
export type RelayTarget =
| { kind: "user"; id: number }
| { kind: "iota"; id: number };
export type SealedRelayOptions = {
nextHop: RelayTarget;
finalRecipientId: number;
metadataRecipients: MTPEncodedBytesInput[];
contentRecipients: MTPEncodedBytesInput[];
};
export type SealedRelayResult = {
type: string;
data: Record<string, unknown>;
};
export function sealedRelayResultFromFrame(frame: MTPFrame): SealedRelayResult {
return {
type: frame.type,
data:
typeof frame.data === "object" &&
frame.data !== null &&
!Array.isArray(frame.data)
? (frame.data as Record<string, unknown>)
: {},
};
}
export class RelayRejectedError extends Error {
public readonly responseType: string;
constructor(responseType: string) {
super(`Relay rejected with ${responseType}`);
this.responseType = responseType;
this.name = "RelayRejectedError";
}
}
export function requireRelaySuccess(response: SealedRelayResult): void {
if (response.type !== "Success") {
throw new RelayRejectedError(response.type);
}
}
export type SealedRelaySend = (
type: string,
data: MTPDataValueInput,
options: SealedRelayOptions,
) => Promise<SealedRelayResult>;
export type MTPExchange = {
type: keyof typeof mtpSchemas & string;
data: unknown;
@ -29,6 +82,7 @@ export type MTPInterceptor = (exchange: MTPExchange) => void | Promise<void>;
export type MTPContextType = {
send: BoundSendFn;
sendSealedRelay: SealedRelaySend;
subscribe: MTPSubscriptionFunction<typeof mtpSchemas>;
addInterceptor: (interceptor: MTPInterceptor) => () => void;
readyState: number;

View file

@ -26,6 +26,8 @@ import {
MTPContext,
type ProtocolMessage,
removeMissingContacts,
type SealedRelayResult,
type SealedRelaySend,
useMessageHandlers,
} from "./mtpContext";
@ -233,12 +235,26 @@ export function TauriProvider(props: {
},
[connection, interceptorsRef],
);
const sendSealedRelay = useCallback<SealedRelaySend>(
async (type, data, options) => {
return invoke<SealedRelayResult>("mtp_send_sealed_relay", {
typeName: type,
data,
nextHop: options.nextHop,
finalRecipientId: options.finalRecipientId,
metadataRecipients: options.metadataRecipients,
contentRecipients: options.contentRecipients,
});
},
[],
);
const connected = snapshot.readyState === ConnectionState.Connected;
return (
<MTPContext.Provider
value={{
send,
sendSealedRelay,
subscribe,
addInterceptor,
readyState: snapshot.readyState,