(feat): remove mtp loading screen
Some checks failed
/ build-web (push) Failing after 4m35s
/ build-desktop (linux) (push) Failing after 4m37s
/ build-mobile (push) Failing after 6m40s
/ release (push) Has been skipped

(fix): weird dedupe bugs
(qol): update todo
This commit is contained in:
Alois 2026-07-03 21:49:13 +02:00
commit 69149b73bd
6 changed files with 86 additions and 61 deletions

View file

@ -25,13 +25,13 @@ import CallPopout from "@tensamin/call/popout";
import ChatContext from "@tensamin/chat/context"; import ChatContext from "@tensamin/chat/context";
import { useInitializeCall } from "@tensamin/call/store"; import { useInitializeCall } from "@tensamin/call/store";
import { Provider as MTPProvider } from "@tensamin/mtp"; import { Provider as MTPProvider } from "@tensamin/mtp";
import UserContext from "@tensamin/user/context"; import UserProvider from "@tensamin/user/context";
import DeeplinkContext from "@tensamin/tauri/deeplinkHandler"; import DeeplinkContext from "@tensamin/tauri/deeplinkHandler";
import NotificationsProvider from "@tensamin/notifications/context"; import NotificationsProvider from "@tensamin/notifications/context";
import TAuthWrapper from "@tensamin/tauth/context"; import TAuthWrapper from "@tensamin/tauth/context";
import { ThemeProvider, useTheme } from "@tensamin/ui"; import { ErrorScreen, ThemeProvider, useTheme } from "@tensamin/ui";
import z from "zod"; import z from "zod";
import { useEffect, useRef, useState, type ReactNode } from "react"; import { useEffect, useRef, useState, type ReactNode } from "react";
@ -256,7 +256,7 @@ function AppShell() {
return ( return (
<MTPProvider> <MTPProvider>
<Session> <Session>
<UserContext> <UserProvider>
<CallInit /> <CallInit />
<CallPopout /> <CallPopout />
<TAuthWrapper> <TAuthWrapper>
@ -268,7 +268,7 @@ function AppShell() {
</ChatContext> </ChatContext>
</AppLayout> </AppLayout>
</TAuthWrapper> </TAuthWrapper>
</UserContext> </UserProvider>
</Session> </Session>
</MTPProvider> </MTPProvider>
); );
@ -281,6 +281,12 @@ function CallInit() {
const rootRoute = createRootRoute({ const rootRoute = createRootRoute({
component: RootShell, component: RootShell,
errorComponent: ({ error }: { error: Error }) => (
<ErrorScreen
description={error.message}
error={"Error in TanStack Router: " + error.name}
/>
),
}); });
const appRoute = createRoute({ const appRoute = createRoute({

View file

@ -114,6 +114,9 @@ export default defineConfig({
"@tensamin/crypto", "@tensamin/crypto",
"@tensamin/storage", "@tensamin/storage",
"@tensamin/mtp", "@tensamin/mtp",
"@tensamin/user",
"@tensamin/tauri",
"@tensamin/chat",
"@codemirror/commands", "@codemirror/commands",
"@codemirror/lang-markdown", "@codemirror/lang-markdown",
"@codemirror/state", "@codemirror/state",

View file

@ -10,8 +10,11 @@ import {
} from "react"; } from "react";
import { isTauri } from "@tauri-apps/api/core"; import { isTauri } from "@tauri-apps/api/core";
import { onResume } from "tauri-plugin-app-events-api"; import { onResume } from "tauri-plugin-app-events-api";
import { ConnectionState, MTPClient } from "mtp"; import { ConnectionState, MTPClient, codec } from "mtp";
import type { z } from "zod"; import { type z } from "zod";
import createAsyncQueue, {
createQueuedFunc,
} from "@tensamin/shared/asyncQueue";
import { import {
type Calls, type Calls,
@ -20,9 +23,8 @@ import {
mtp as schemas, mtp as schemas,
type MTP as Schemas, type MTP as Schemas,
} from "@tensamin/shared/data"; } from "@tensamin/shared/data";
import { log } from "@tensamin/shared/log"; import { log, toast } from "@tensamin/shared/log";
import { useStorage } from "@tensamin/storage/context"; import { useStorage } from "@tensamin/storage/context";
import { ErrorScreen, LoadingScreen as Loading } from "@tensamin/ui";
import { import {
PING_INTERVAL, PING_INTERVAL,
@ -91,6 +93,8 @@ type ContextType = {
freshContacts: Contacts; freshContacts: Contacts;
freshCommunities: Communities; freshCommunities: Communities;
freshCalls: Calls; freshCalls: Calls;
contextReady: boolean;
loadingDescription: string;
}; };
const MTPContext = createContext<ContextType | undefined>(undefined); const MTPContext = createContext<ContextType | undefined>(undefined);
@ -161,9 +165,6 @@ export function Provider(props: {
const [ownPing, setOwnPing] = useState<number>(0); const [ownPing, setOwnPing] = useState<number>(0);
const [iotaPing, setIotaPing] = useState<number>(0); const [iotaPing, setIotaPing] = useState<number>(0);
const [error, setError] = useState("");
const [errorDescription, setErrorDescription] = useState("");
const [freshCommunities, setFreshCommunities] = useState<Communities>([]); const [freshCommunities, setFreshCommunities] = useState<Communities>([]);
const [freshContacts, setFreshContacts] = useState<Contacts>([]); const [freshContacts, setFreshContacts] = useState<Contacts>([]);
const [freshCalls, setFreshCalls] = useState<Calls>([]); const [freshCalls, setFreshCalls] = useState<Calls>([]);
@ -234,9 +235,10 @@ export function Provider(props: {
return subscribe("error_no_iota", () => { return subscribe("error_no_iota", () => {
setIdentified(false); setIdentified(false);
setIdentifying(false); setIdentifying(false);
setError("We couldn't reach your Iota"); toast(
setErrorDescription( "error",
"You could try to restart your Iota, check for updates or check your network connection.", "We couldn't reach your Iota",
"Check your network connection and try restarting your Iota",
); );
}); });
}, [connected, subscribe]); }, [connected, subscribe]);
@ -303,9 +305,10 @@ export function Provider(props: {
if (disposed || reconnectScheduled) return; if (disposed || reconnectScheduled) return;
if (attempts >= RECONNECT_TRIES) { if (attempts >= RECONNECT_TRIES) {
setError("Connection Failed"); toast(
setErrorDescription( "error",
"Unable to connect to the server after multiple attempts. Please check your internet connection or try again later.", "Connection Failed",
"Unable to connect to the Omikron after multiple attempts. Cehck your network connection.",
); );
log(0, "mtp", "red", "Reconnection attempts exhausted", reason); log(0, "mtp", "red", "Reconnection attempts exhausted", reason);
return; return;
@ -321,15 +324,16 @@ export function Provider(props: {
}; };
async function connect() { async function connect() {
if (disposed || props.blockConnection) { if (disposed || props.blockConnection) return;
return;
}
try { try {
setIdentified(false); setIdentified(false);
setIdentifying(false); setIdentifying(false);
await MTPClient.init(); await MTPClient.init();
const rawOmikronData = await fetch(mtpUrl ?? "").then((res) =>
codec.decode(res.arrayBuffer()),
);
const client = await MTPClient.create({ const client = await MTPClient.create({
url: mtpUrl ?? "", url: mtpUrl ?? "",
descriptor: "client", descriptor: "client",
@ -383,8 +387,6 @@ export function Provider(props: {
scheduleReconnectReset(); scheduleReconnectReset();
setReadyState(client.state); setReadyState(client.state);
setIdentifying(true); setIdentifying(true);
setError("");
setErrorDescription("");
await client.auth(); await client.auth();
const finalResponse = await authPayload; const finalResponse = await authPayload;
@ -427,9 +429,6 @@ export function Provider(props: {
clearReconnectResetTimer(); clearReconnectResetTimer();
attempts = 0; attempts = 0;
reconnectScheduled = false; reconnectScheduled = false;
setError("");
setErrorDescription("");
await connect(); await connect();
} }
@ -459,53 +458,39 @@ export function Provider(props: {
}; };
}, [mtpUrl, props.blockConnection]); }, [mtpUrl, props.blockConnection]);
// Loading bar // Async queue
const progress = useMemo(() => {
if (!mtpUrl) return 10;
if (readyState === ConnectionState.Connecting) return 30;
if (!connected) return 45;
if (identifying) return 75;
if (!identified) return 90;
return 100;
}, [connected, identified, identifying, readyState, mtpUrl]);
const loadingTitle = useMemo(() => {
if (!mtpUrl) return "Looking up configuration";
if (readyState === ConnectionState.Connecting || !connected)
return "Connecting to Tensamin";
if (identifying || !identified) return "Identifying secure session";
return "Loading";
}, [connected, identified, identifying, readyState, mtpUrl]);
const loadingDescription = useMemo(() => { const loadingDescription = useMemo(() => {
if (!mtpUrl) return "Loading connection details"; if (!mtpUrl) return "Loading connection details";
if (readyState === ConnectionState.Connecting || !connected) { if (readyState === ConnectionState.Connecting || !connected) {
return "Establishing transport channel"; return "Establishing transport channel";
} }
if (identifying || !identified) return "Waiting for authenticated session"; if (identifying || !identified) return "Waiting for authenticated session";
return undefined; return "Loading...";
}, [connected, identified, identifying, readyState, mtpUrl]); }, [connected, identified, identifying, readyState, mtpUrl]);
const contextReady = connected && identified && mtpUrl !== null;
// Return const mtpRef = useMemo(
if (error !== "" && errorDescription !== "") { () =>
return <ErrorScreen error={error} description={errorDescription} />; createAsyncQueue<{
} send: typeof send;
subscribe: typeof subscribe;
if (!connected || !identified || !mtpUrl) { subscribePush: typeof subscribePush;
return ( }>(),
<Loading [],
progress={progress} );
title={loadingTitle} useEffect(() => {
description={loadingDescription} if (connected && identified && mtpUrl) {
fullscreen mtpRef.set({
/> send,
); subscribe,
} subscribePush,
});
}
}, [connected, identified, mtpUrl, send, subscribe, subscribePush, mtpRef]);
return ( return (
<MTPContext.Provider <MTPContext.Provider
value={{ value={{
send, send: createQueuedFunc(() => (contextReady ? send : null)),
subscribe, subscribe,
subscribePush, subscribePush,
readyState, readyState,
@ -515,6 +500,8 @@ export function Provider(props: {
freshContacts, freshContacts,
freshCommunities, freshCommunities,
freshCalls, freshCalls,
contextReady,
loadingDescription,
}} }}
> >
{props.children} {props.children}

1
packages/mtp/todo.md Normal file
View file

@ -0,0 +1 @@
- Get omikron url & public key from omega

View file

@ -4,6 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"exports": { "exports": {
"./asyncQueue": "./src/asyncQueue.ts",
"./code": "./src/code.ts", "./code": "./src/code.ts",
"./data": "./src/data.ts", "./data": "./src/data.ts",
"./desktopMedia": "./src/desktopMedia.tsx", "./desktopMedia": "./src/desktopMedia.tsx",

View file

@ -0,0 +1,27 @@
export default function createAsyncQueue<T>() {
let resolve!: (value: T) => void;
const promise = new Promise<T>((r) => {
resolve = r;
});
return {
set: resolve,
get: () => promise,
};
}
// eslint-disable-next-line
type AnyFn = (...args: any[]) => any;
export function createQueuedFunc<F extends AnyFn>(
getFn: () => F | null | undefined,
) {
return (async (...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> => {
while (!getFn()) {
await new Promise((r) => setTimeout(r, 50));
}
return await getFn()!(...args);
}) as F;
}