diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx
index bbc399e..0a35819 100644
--- a/apps/web/src/index.tsx
+++ b/apps/web/src/index.tsx
@@ -25,13 +25,13 @@ import CallPopout from "@tensamin/call/popout";
import ChatContext from "@tensamin/chat/context";
import { useInitializeCall } from "@tensamin/call/store";
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 NotificationsProvider from "@tensamin/notifications/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 { useEffect, useRef, useState, type ReactNode } from "react";
@@ -256,7 +256,7 @@ function AppShell() {
return (
-
+
@@ -268,7 +268,7 @@ function AppShell() {
-
+
);
@@ -281,6 +281,12 @@ function CallInit() {
const rootRoute = createRootRoute({
component: RootShell,
+ errorComponent: ({ error }: { error: Error }) => (
+
+ ),
});
const appRoute = createRoute({
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
index a224653..c2571df 100644
--- a/apps/web/vite.config.ts
+++ b/apps/web/vite.config.ts
@@ -114,6 +114,9 @@ export default defineConfig({
"@tensamin/crypto",
"@tensamin/storage",
"@tensamin/mtp",
+ "@tensamin/user",
+ "@tensamin/tauri",
+ "@tensamin/chat",
"@codemirror/commands",
"@codemirror/lang-markdown",
"@codemirror/state",
diff --git a/packages/mtp/src/context.tsx b/packages/mtp/src/context.tsx
index 6b75c6e..9d4e6f6 100644
--- a/packages/mtp/src/context.tsx
+++ b/packages/mtp/src/context.tsx
@@ -10,8 +10,11 @@ import {
} from "react";
import { isTauri } from "@tauri-apps/api/core";
import { onResume } from "tauri-plugin-app-events-api";
-import { ConnectionState, MTPClient } from "mtp";
-import type { z } from "zod";
+import { ConnectionState, MTPClient, codec } from "mtp";
+import { type z } from "zod";
+import createAsyncQueue, {
+ createQueuedFunc,
+} from "@tensamin/shared/asyncQueue";
import {
type Calls,
@@ -20,9 +23,8 @@ import {
mtp as schemas,
type MTP as Schemas,
} from "@tensamin/shared/data";
-import { log } from "@tensamin/shared/log";
+import { log, toast } from "@tensamin/shared/log";
import { useStorage } from "@tensamin/storage/context";
-import { ErrorScreen, LoadingScreen as Loading } from "@tensamin/ui";
import {
PING_INTERVAL,
@@ -91,6 +93,8 @@ type ContextType = {
freshContacts: Contacts;
freshCommunities: Communities;
freshCalls: Calls;
+ contextReady: boolean;
+ loadingDescription: string;
};
const MTPContext = createContext(undefined);
@@ -161,9 +165,6 @@ export function Provider(props: {
const [ownPing, setOwnPing] = useState(0);
const [iotaPing, setIotaPing] = useState(0);
- const [error, setError] = useState("");
- const [errorDescription, setErrorDescription] = useState("");
-
const [freshCommunities, setFreshCommunities] = useState([]);
const [freshContacts, setFreshContacts] = useState([]);
const [freshCalls, setFreshCalls] = useState([]);
@@ -234,9 +235,10 @@ export function Provider(props: {
return subscribe("error_no_iota", () => {
setIdentified(false);
setIdentifying(false);
- setError("We couldn't reach your Iota");
- setErrorDescription(
- "You could try to restart your Iota, check for updates or check your network connection.",
+ toast(
+ "error",
+ "We couldn't reach your Iota",
+ "Check your network connection and try restarting your Iota",
);
});
}, [connected, subscribe]);
@@ -303,9 +305,10 @@ export function Provider(props: {
if (disposed || reconnectScheduled) return;
if (attempts >= RECONNECT_TRIES) {
- setError("Connection Failed");
- setErrorDescription(
- "Unable to connect to the server after multiple attempts. Please check your internet connection or try again later.",
+ toast(
+ "error",
+ "Connection Failed",
+ "Unable to connect to the Omikron after multiple attempts. Cehck your network connection.",
);
log(0, "mtp", "red", "Reconnection attempts exhausted", reason);
return;
@@ -321,15 +324,16 @@ export function Provider(props: {
};
async function connect() {
- if (disposed || props.blockConnection) {
- return;
- }
+ if (disposed || props.blockConnection) return;
try {
setIdentified(false);
setIdentifying(false);
await MTPClient.init();
+ const rawOmikronData = await fetch(mtpUrl ?? "").then((res) =>
+ codec.decode(res.arrayBuffer()),
+ );
const client = await MTPClient.create({
url: mtpUrl ?? "",
descriptor: "client",
@@ -383,8 +387,6 @@ export function Provider(props: {
scheduleReconnectReset();
setReadyState(client.state);
setIdentifying(true);
- setError("");
- setErrorDescription("");
await client.auth();
const finalResponse = await authPayload;
@@ -427,9 +429,6 @@ export function Provider(props: {
clearReconnectResetTimer();
attempts = 0;
reconnectScheduled = false;
- setError("");
- setErrorDescription("");
-
await connect();
}
@@ -459,53 +458,39 @@ export function Provider(props: {
};
}, [mtpUrl, props.blockConnection]);
- // Loading bar
- 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]);
-
+ // Async queue
const loadingDescription = useMemo(() => {
if (!mtpUrl) return "Loading connection details";
if (readyState === ConnectionState.Connecting || !connected) {
return "Establishing transport channel";
}
if (identifying || !identified) return "Waiting for authenticated session";
- return undefined;
+ return "Loading...";
}, [connected, identified, identifying, readyState, mtpUrl]);
-
- // Return
- if (error !== "" && errorDescription !== "") {
- return ;
- }
-
- if (!connected || !identified || !mtpUrl) {
- return (
-
- );
- }
+ const contextReady = connected && identified && mtpUrl !== null;
+ const mtpRef = useMemo(
+ () =>
+ createAsyncQueue<{
+ send: typeof send;
+ subscribe: typeof subscribe;
+ subscribePush: typeof subscribePush;
+ }>(),
+ [],
+ );
+ useEffect(() => {
+ if (connected && identified && mtpUrl) {
+ mtpRef.set({
+ send,
+ subscribe,
+ subscribePush,
+ });
+ }
+ }, [connected, identified, mtpUrl, send, subscribe, subscribePush, mtpRef]);
return (
(contextReady ? send : null)),
subscribe,
subscribePush,
readyState,
@@ -515,6 +500,8 @@ export function Provider(props: {
freshContacts,
freshCommunities,
freshCalls,
+ contextReady,
+ loadingDescription,
}}
>
{props.children}
diff --git a/packages/mtp/todo.md b/packages/mtp/todo.md
new file mode 100644
index 0000000..5224278
--- /dev/null
+++ b/packages/mtp/todo.md
@@ -0,0 +1 @@
+- Get omikron url & public key from omega
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 26c0d9f..f9f5ff6 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -4,6 +4,7 @@
"version": "0.0.0",
"type": "module",
"exports": {
+ "./asyncQueue": "./src/asyncQueue.ts",
"./code": "./src/code.ts",
"./data": "./src/data.ts",
"./desktopMedia": "./src/desktopMedia.tsx",
diff --git a/packages/shared/src/asyncQueue.ts b/packages/shared/src/asyncQueue.ts
new file mode 100644
index 0000000..60d12f3
--- /dev/null
+++ b/packages/shared/src/asyncQueue.ts
@@ -0,0 +1,27 @@
+export default function createAsyncQueue() {
+ let resolve!: (value: T) => void;
+
+ const promise = new Promise((r) => {
+ resolve = r;
+ });
+
+ return {
+ set: resolve,
+ get: () => promise,
+ };
+}
+
+// eslint-disable-next-line
+type AnyFn = (...args: any[]) => any;
+
+export function createQueuedFunc(
+ getFn: () => F | null | undefined,
+) {
+ return (async (...args: Parameters): Promise>> => {
+ while (!getFn()) {
+ await new Promise((r) => setTimeout(r, 50));
+ }
+
+ return await getFn()!(...args);
+ }) as F;
+}