diff --git a/mtp-type-maps b/mtp-type-maps index 6d97b5b..594646a 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit 6d97b5b9935b15d957444d38609378c66a24262a +Subproject commit 594646ac39d986f0787aa614a99d580035a67318 diff --git a/packages/mtp/src/context.tsx b/packages/mtp/src/context.tsx index ee3149c..4f5f8ea 100644 --- a/packages/mtp/src/context.tsx +++ b/packages/mtp/src/context.tsx @@ -144,7 +144,7 @@ export function Provider(props: { children: ReactNode; blockConnection?: boolean; }) { - const { load } = useStorage(); + const { load, save } = useStorage(); const [readyState, setReadyState] = useState( ConnectionState.Disconnected, @@ -300,13 +300,19 @@ export function Provider(props: { await MTPClient.init(); - const [userId, keyring] = await Promise.all([ + const [userId, keyring, storedSessionId] = await Promise.all([ load("user_id"), load("mtp_keyring"), + load("session_id"), ]); if (!userId || !keyring) { throw new Error("Missing login credentials"); } + let sessionId = storedSessionId; + if (!Number.isSafeInteger(sessionId) || sessionId <= 0) { + sessionId = Date.now(); + await save("session_id", sessionId); + } const forcedOmikronUrl = await load("forced_omikron_url"); const forcedOmikronPublicKey = await load("forced_omikron_public_key"); @@ -404,27 +410,6 @@ export function Provider(props: { clientRef.current = client; setReadyState(client.state); - const authPayload = new Promise< - ProtocolMessage<"IdentificationResponse"> - >((resolve, reject) => { - const unsubscribe = client.subscribe( - "IdentificationResponse", - (message) => { - try { - unsubscribe(); - if (message.type.startsWith("Error")) { - reject(new Error(`Authentication failed: ${message.type}`)); - return; - } - resolve(validateResponse("IdentificationResponse", message)); - } catch (authPayloadError) { - unsubscribe(); - reject(authPayloadError); - } - }, - ); - }); - clearReconnectTimer(); // Schedule reconnect reset @@ -438,7 +423,31 @@ export function Provider(props: { setIdentifying(true); await client.auth(); - const finalResponse = await authPayload; + const finalResponse = validateResponse( + "ClientStateSync", + await client.request("ClientConnected", { + SessionId: sessionId, + VersionNumber: 0, + CacheValid: false, + CacheSchemaVersion: 0, + }), + ); + + if (finalResponse.type.startsWith("Error")) { + throw new Error( + `State synchronization failed: ${finalResponse.type}`, + ); + } + + const acknowledgement = await client.request("ClientStateAck", { + SessionId: sessionId, + VersionNumber: finalResponse.data.VersionNumber, + }); + if (acknowledgement.type.startsWith("Error")) { + throw new Error( + `State acknowledgement failed: ${acknowledgement.type}`, + ); + } if (disposed || clientRef.current !== client) return; @@ -451,11 +460,15 @@ export function Provider(props: { } catch (connectError) { if (disposed) return; cleanup(); + const connectErrorMessage = + connectError instanceof Error + ? connectError.message + : String(connectError ?? "Unknown error"); log( 0, "mtp", "red", - "Connection/authentication attempt failed", + `Connection/authentication attempt failed: ${connectErrorMessage}`, getProtocolErrorDetails(connectError) ?? connectError, ); @@ -465,10 +478,7 @@ export function Provider(props: { log(0, "mtp", "red", "Reconnection attempts exhausted", connectError); sonnerToast.error("Connection failed", { id: "mtp-connection-toast", - description: - connectError instanceof Error - ? connectError.message.split(":")[0] - : String(connectError ?? "Unknown error"), + description: connectErrorMessage.split(":")[0], icon: null, duration: Infinity, closeButton: true, @@ -531,7 +541,7 @@ export function Provider(props: { setIdentifying(false); sonnerToast.dismiss("mtp-connection-toast"); }; - }, [mtpUrl, props.blockConnection, load]); + }, [mtpUrl, props.blockConnection, load, save]); // No Iota check useEffect(() => { diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index ee3152f..4d2c021 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -106,7 +106,7 @@ const authPayload = z.object({ Contacts: z .array( z.object({ - LastMessageAt: z.number(), + LastMessageAt: z.number().default(0), UserId: z.number(), LastMessage: z .object({ @@ -114,7 +114,7 @@ const authPayload = z.object({ SenderId: z.number(), }) .optional(), - Messages: z.array(Message), + Messages: z.array(Message).default([]), }), ) .default([]), @@ -129,6 +129,16 @@ const authPayload = z.object({ .default([]), }); +const clientStateSync = authPayload.extend({ + SessionId: z.number().int().positive(), + VersionNumber: z.number().int().nonnegative(), + CacheSchemaVersion: z.number().int().nonnegative(), + SyncMode: z.enum(["full", "delta"]), + Messages: z.array(Message).default([]), + DeletedMessageIds: z.array(z.number()).default([]), + DeletedContactIds: z.array(z.number()).default([]), +}); + export type Contacts = z.infer; export type Communities = z.infer; export type Calls = z.infer; @@ -184,6 +194,26 @@ export const mtp = { request: z.object({}).optional(), response: authPayload, }, + ClientConnected: { + request: z.object({ + SessionId: z.number().int().positive(), + VersionNumber: z.number().int().nonnegative(), + CacheValid: z.boolean(), + CacheSchemaVersion: z.number().int().nonnegative(), + }), + response: clientStateSync, + }, + ClientStateSync: { + request: z.object({}).optional(), + response: clientStateSync, + }, + ClientStateAck: { + request: z.object({ + SessionId: z.number().int().positive(), + VersionNumber: z.number().int().nonnegative(), + }), + response: z.object({}), + }, GetUserData: { request: z.object({ UserId: z.number().optional(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50e0cc8..5b017bc 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.8/methanium-ui.tgz - mtp: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-3919eb46fd/mtp-0.2.0.tgz + mtp: link:../mtp importers: @@ -16,8 +16,8 @@ importers: specifier: https://git.methanium.net/methanium/ui/releases/download/0.0.8/methanium-ui.tgz version: https://git.methanium.net/methanium/ui/releases/download/0.0.8/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-3919eb46fd/mtp-0.2.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-3919eb46fd/mtp-0.2.0.tgz + specifier: link:../mtp + version: link:../mtp 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-3919eb46fd/mtp-0.2.0.tgz - version: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-3919eb46fd/mtp-0.2.0.tgz + specifier: link:../../../mtp + version: link:../../../mtp react: specifier: ^19.2.0 version: 19.2.7 @@ -4006,10 +4006,6 @@ 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-3919eb46fd/mtp-0.2.0.tgz: - resolution: {integrity: sha512-Adfwu1N48pSNZXaLpBW//uAeFaDCHYMOjWg+APG4TxEwCyQjXygCxBuEdUOjOxVZP9cK8L/K5Mf5HBO1+bd4HA==, tarball: https://git.methanium.net/methanium/mtp/releases/download/0.2.0-3919eb46fd/mtp-0.2.0.tgz} - version: 0.2.0 - nanoid@3.3.15: resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -8254,10 +8250,6 @@ snapshots: ms@2.1.3: {} - mtp@https://git.methanium.net/methanium/mtp/releases/download/0.2.0-3919eb46fd/mtp-0.2.0.tgz: - dependencies: - yaml: 2.9.0 - nanoid@3.3.15: {} nanoid@3.3.16: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 96ed25a..ee452a5 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.8/methanium-ui.tgz" - mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.2.0-3919eb46fd/mtp-0.2.0.tgz" + mtp: "link:../mtp"