diff --git a/TODO b/TODO
index db6ebe1..3ec0948 100644
--- a/TODO
+++ b/TODO
@@ -2,5 +2,6 @@
- Add packages/hotkeys/
- Full accessability
- Add settings saving & update onboarding to use it
+- Add onboarding page to load one profile during onboarding
-> Most folders in packages/ have specific todos
diff --git a/apps/web/src/components/modals/basic.tsx b/apps/web/src/components/modals/basic.tsx
index 4a452ae..8a0a611 100644
--- a/apps/web/src/components/modals/basic.tsx
+++ b/apps/web/src/components/modals/basic.tsx
@@ -27,6 +27,7 @@ export function Basic({
return (
}
+ nativeButton={false}
variant="outline"
className="animate-in fade-in duration-300 h-auto w-full justify-start gap-2.5 rounded-xl min-h-12.5!"
>
diff --git a/mtp-type-maps b/mtp-type-maps
index fee0a8f..11a1d79 160000
--- a/mtp-type-maps
+++ b/mtp-type-maps
@@ -1 +1 @@
-Subproject commit fee0a8fa9994b6a2d04e0966387c713c47c39b7c
+Subproject commit 11a1d79409857b734e948dd8c3e28e6ba721d15f
diff --git a/packages/mtp/src/context.tsx b/packages/mtp/src/context.tsx
index 23a2363..124ec6f 100644
--- a/packages/mtp/src/context.tsx
+++ b/packages/mtp/src/context.tsx
@@ -237,6 +237,7 @@ export function Provider(props: {
let reconnectScheduled = false;
let disposed = false;
let resumeListenerRegistered = false;
+ let connectionGeneration = 0;
const clearReconnectTimer = () => {
if (!reconnectTimer) return;
@@ -251,16 +252,52 @@ export function Provider(props: {
reconnectResetTimer = null;
};
+ const scheduleReconnect = (error: unknown) => {
+ if (disposed || reconnectScheduled) return;
+ if (attempts >= RECONNECT_TRIES) {
+ log(0, "mtp", "red", "Reconnection attempts exhausted", error);
+ sonnerToast.error("Connection failed", {
+ id: "mtp-connection-toast",
+ description:
+ error instanceof Error ? error.message.split(":")[0] : "Connection lost",
+ icon: null,
+ duration: Infinity,
+ closeButton: true,
+ promise: null,
+ } as unknown as Parameters[1]);
+ return;
+ }
+
+ attempts += 1;
+ sonnerToast.loading(
+ `Reconnecting to server... (attempt ${attempts} of ${RECONNECT_TRIES})`,
+ { id: "mtp-connection-toast" },
+ );
+ reconnectScheduled = true;
+ reconnectTimer = setTimeout(() => {
+ reconnectScheduled = false;
+ reconnectTimer = null;
+ void connect();
+ }, RETRY_INTERVAL);
+ };
+
async function connect() {
if (disposed || props.blockConnection) return;
+ const generation = ++connectionGeneration;
+ let client: Awaited> | null = null;
+ let failed = false;
const cleanup = () => {
- clientRef.current?.disconnect();
- clientRef.current = null;
+ client?.disconnect();
+ if (clientRef.current === client) {
+ clientRef.current = null;
+ }
clearReconnectResetTimer();
- setReadyState(ConnectionState.Disconnected);
- setIdentified(false);
- setIdentifying(false);
+ if (generation === connectionGeneration) {
+ setReadyState(ConnectionState.Disconnected);
+ setIdentified(false);
+ setIdentifying(false);
+ }
};
try {
setIdentified(false);
@@ -324,7 +361,7 @@ export function Provider(props: {
log(2, "mtp", "green", "Connecting to: " + url);
- const client = await MTPClient.create({
+ client = await MTPClient.create({
url,
credentials: {
clientId: userId,
@@ -335,9 +372,20 @@ export function Provider(props: {
pings: true,
logger: (event) => {
if (event.type === "state") {
- setReadyState(
- clientRef.current?.state ?? ConnectionState.Disconnected,
- );
+ if (generation !== connectionGeneration) return;
+ const state = client?.state ?? ConnectionState.Disconnected;
+ setReadyState(state);
+ if (
+ state === ConnectionState.Disconnected &&
+ clientRef.current === client &&
+ !failed
+ ) {
+ failed = true;
+ clientRef.current = null;
+ setIdentified(false);
+ setIdentifying(false);
+ scheduleReconnect(new Error("MTP connection lost"));
+ }
}
if (event.type !== "Pong" && event.type !== "Ping") {
@@ -364,13 +412,14 @@ export function Provider(props: {
},
});
- if (disposed) {
+ if (disposed || generation !== connectionGeneration) {
client.disconnect();
return;
}
+ const activeClient = client;
- clientRef.current = client;
- setReadyState(client.state);
+ clientRef.current = activeClient;
+ setReadyState(activeClient.state);
clearReconnectTimer();
@@ -381,7 +430,7 @@ export function Provider(props: {
reconnectResetTimer = null;
}, RECONNECT_RESET * 1_000);
- setReadyState(client.state);
+ setReadyState(activeClient.state);
setIdentifying(true);
const stateSync = new Promise>(
@@ -390,7 +439,7 @@ export function Provider(props: {
unsubscribe();
reject(new Error("Initial state synchronization timed out"));
}, 120_000);
- const unsubscribe = client.subscribe(
+ const unsubscribe = activeClient.subscribe(
"ClientStateSync",
(message) => {
clearTimeout(timeout);
@@ -404,7 +453,10 @@ export function Provider(props: {
);
},
);
- const [, finalResponse] = await Promise.all([client.auth(), stateSync]);
+ const [, finalResponse] = await Promise.all([
+ activeClient.auth(),
+ stateSync,
+ ]);
if (finalResponse.type.startsWith("Error")) {
throw new Error(
@@ -412,7 +464,7 @@ export function Provider(props: {
);
}
- const acknowledgement = await client.request("ClientStateAck", {
+ const acknowledgement = await activeClient.request("ClientStateAck", {
SessionId: finalResponse.data.SessionId,
VersionNumber: finalResponse.data.VersionNumber,
});
@@ -422,7 +474,7 @@ export function Provider(props: {
);
}
- if (disposed || clientRef.current !== client) return;
+ if (disposed || clientRef.current !== activeClient) return;
setFreshContacts(finalResponse.data.Contacts);
setFreshCommunities(finalResponse.data.Communities);
@@ -431,7 +483,11 @@ export function Provider(props: {
setIdentified(true);
resolveConnectionRef.current?.();
} catch (connectError) {
- if (disposed) return;
+ if (disposed || generation !== connectionGeneration) {
+ client?.disconnect();
+ return;
+ }
+ failed = true;
cleanup();
const connectErrorMessage =
connectError instanceof Error
@@ -445,41 +501,14 @@ export function Provider(props: {
getProtocolErrorDetails(connectError) ?? connectError,
);
- // Schedule reconnect
- if (disposed || reconnectScheduled) return;
- if (attempts >= RECONNECT_TRIES) {
- log(0, "mtp", "red", "Reconnection attempts exhausted", connectError);
- sonnerToast.error("Connection failed", {
- id: "mtp-connection-toast",
- description: connectErrorMessage.split(":")[0],
- icon: null,
- duration: Infinity,
- closeButton: true,
- promise: null,
- } as unknown as Parameters[1]);
- return;
- }
-
- attempts += 1;
-
- // Show loading toast
- sonnerToast.loading(
- `Reconnecting to server... (attempt ${attempts} of ${RECONNECT_TRIES})`,
- { id: "mtp-connection-toast" },
- );
-
- reconnectScheduled = true;
- reconnectTimer = setTimeout(() => {
- reconnectScheduled = false;
- reconnectTimer = null;
- void connect();
- }, RETRY_INTERVAL);
+ scheduleReconnect(connectError);
}
}
async function reconnectAfterResume() {
if (disposed) return;
+ connectionGeneration += 1;
clientRef.current?.disconnect();
clientRef.current = null;
clearReconnectTimer();
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0ce1183..2ea016d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,7 +6,7 @@ settings:
overrides:
'@methanium/ui': https://git.methanium.net/methanium/ui/releases/download/0.0.10/methanium-ui.tgz
- mtp: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz
+ mtp: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz
importers:
@@ -16,8 +16,8 @@ importers:
specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.10/methanium-ui.tgz
version: https://git.methanium.net/methanium/ui/releases/download/0.0.10/methanium-ui.tgz(@date-fns/tz@1.5.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1)(typescript@6.0.3)
mtp:
- specifier: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz
- version: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz
+ specifier: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz
+ version: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz
sonner:
specifier: ^2.0.7
version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
@@ -700,8 +700,8 @@ importers:
specifier: ^1.14.0
version: 1.23.0(react@19.2.7)
mtp:
- specifier: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz
- version: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz
+ specifier: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz
+ version: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz
react:
specifier: ^19.2.0
version: 19.2.7
@@ -4006,8 +4006,8 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- mtp@https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz:
- resolution: {integrity: sha512-kvJ98CMrr/ZCQr1oVhys9D3c+/OZAl6Oar3DjI6bvnYLelpjnXbrtq/8A31ktbipgMsaRInQuFBKRYMBm/f8OQ==, tarball: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz}
+ mtp@https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz:
+ resolution: {integrity: sha512-WcXHd53aDM0zQfO8eaUnK5tpnRP73GlWjwAMMcstcVAIWrvyFGuINkCRNEkb2RsNk9h57UMhxElCZeBkFrVmCg==, tarball: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz}
version: 0.2.0
nanoid@3.3.15:
@@ -8254,7 +8254,7 @@ snapshots:
ms@2.1.3: {}
- mtp@https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz:
+ mtp@https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz:
dependencies:
yaml: 2.9.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 27d1055..d0aca66 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -7,4 +7,4 @@ allowBuilds:
esbuild: true
overrides:
"@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.10/methanium-ui.tgz"
- mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-fa271e6/mtp-0.2.0.tgz"
+ mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-6f673ba/mtp-0.2.0.tgz"