TTP -> MTP, A lot of other stuff #21
6 changed files with 86 additions and 61 deletions
(feat): remove mtp loading screen
(fix): weird dedupe bugs (qol): update todo
commit
69149b73bd
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
1
packages/mtp/todo.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
- Get omikron url & public key from omega
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
27
packages/shared/src/asyncQueue.ts
Normal file
27
packages/shared/src/asyncQueue.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue