auth flow fix, validation for auth in steps /uploadContnt

This commit is contained in:
Verticool 2025-02-03 11:04:15 +06:00
parent 7705908484
commit 3e9564d7b9
5 changed files with 147 additions and 139 deletions

View File

@ -23,8 +23,13 @@ export const WelcomeStep = ({ nextStep }: WelcomeStepProps) => {
await auth.mutateAsync(); await auth.mutateAsync();
nextStep(); nextStep();
} else { } else {
try {
await tonConnectUI.openModal(); await tonConnectUI.openModal();
await auth.mutateAsync(); await auth.mutateAsync();
nextStep();
} catch (error) {
console.error('Failed to connect or authenticate:', error);
}
} }
}; };

View File

@ -2,6 +2,7 @@ import ReactPlayer from "react-player/lazy";
import { useTonConnectUI } from "@tonconnect/ui-react"; import { useTonConnectUI } from "@tonconnect/ui-react";
import { useWebApp } from "@vkruglikov/react-telegram-web-app"; import { useWebApp } from "@vkruglikov/react-telegram-web-app";
import { Button } from "~/shared/ui/button";
import { usePurchaseContent, useViewContent } from "~/shared/services/content"; import { usePurchaseContent, useViewContent } from "~/shared/services/content";
import { fromNanoTON } from "~/shared/utils"; import { fromNanoTON } from "~/shared/utils";
import {useCallback, useEffect, useMemo} from "react"; import {useCallback, useEffect, useMemo} from "react";
@ -71,45 +72,9 @@ export const ViewContentPage = () => {
}, []); }, []);
useEffect(() => {
const mainButton = WebApp.MainButton;
const secondaryButton = WebApp.SecondaryButton;
try {
// Set main button color
mainButton.color = '#e40615';
mainButton.textColor = '#FFFFFF';
// Set secondary button color
secondaryButton.color = '#363636';
secondaryButton.textColor = '#FFFFFF';
if (!haveLicense) {
mainButton.text = `Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`;
mainButton.show();
mainButton.onClick(handleBuyContent);
} else {
mainButton.hide();
}
secondaryButton.text = 'Загрузить свой контент';
secondaryButton.show();
secondaryButton.onClick(() => {
WebApp.openTelegramLink('https://t.me/MY_UploaderRobot');
});
return () => {
mainButton.hide();
mainButton.offClick(handleBuyContent);
secondaryButton.hide();
secondaryButton.offClick();
};
} catch (error) {
console.error('Error setting up Telegram WebApp buttons:', error);
}
}, [content, haveLicense, WebApp, handleBuyContent]);
return ( return (
<main className={"flex w-full flex-col gap-[50px] px-4"}> <main className={"min-h-screen flex w-full flex-col gap-[50px] px-4 "}>
{content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && ( {content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && (
<div className={"mt-[30px] h-[314px] w-full"}> <div className={"mt-[30px] h-[314px] w-full"}>
<img <img
@ -146,9 +111,10 @@ export const ViewContentPage = () => {
</p> </p>
</section> </section>
{/* {!haveLicense && <Button <div className="mt-auto pb-2">
{!haveLicense && <Button
onClick={handleBuyContent} onClick={handleBuyContent}
className={"mb-4 mt-[30px] h-[48px]"} className={"mb-4 h-[48px]"}
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`} label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`}
includeArrows={true} includeArrows={true}
/> />
@ -158,9 +124,10 @@ export const ViewContentPage = () => {
onClick={() => { onClick={() => {
WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`); WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`);
}} }}
className={"mb-4 mt-[-20px] h-[48px] bg-darkred"} className={"h-[48px] bg-darkred"}
label={`Загрузить свой контент`} label={`Загрузить свой контент`}
/> */} />
</div>
</main> </main>
); );
}; };

View File

@ -1,4 +1,8 @@
import { ReactNode, useMemo, useState } from "react"; import { useTonConnectUI } from "@tonconnect/ui-react";
import { ReactNode, useEffect, useMemo, useState } from "react";
const CHECK_INTERVAL = 20000;
export const useSteps = ( export const useSteps = (
sections: ({ sections: ({
@ -9,15 +13,24 @@ export const useSteps = (
prevStep(): void; prevStep(): void;
}) => ReactNode[], }) => ReactNode[],
) => { ) => {
const [tonConnectUI] = useTonConnectUI();
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
const nextStep = () => { // If connection is lost, reset the step
return setStep((s) => s + 1); useEffect(() => {
}; const interval = setInterval(() => {
if (!tonConnectUI.connected && step !== 0) {
setStep(0);
}
}, CHECK_INTERVAL);
const prevStep = () => { return () => clearInterval(interval);
return setStep((s) => s - 1); }, []);
};
const nextStep = () => setStep((s) => s + 1);
const prevStep = () => setStep((s) => s - 1);
const ActiveSection = useMemo(() => { const ActiveSection = useMemo(() => {
return sections({ nextStep, prevStep })[step]; return sections({ nextStep, prevStep })[step];

View File

@ -13,80 +13,103 @@ export const useAuth = () => {
const [tonConnectUI] = useTonConnectUI(); const [tonConnectUI] = useTonConnectUI();
const interval = useRef<ReturnType<typeof setInterval> | undefined>(); const interval = useRef<ReturnType<typeof setInterval> | undefined>();
return useMutation(["auth"], async () => { const waitForWalletProof = async () => {
clearInterval(interval.current); return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("Timeout waiting for proof")), 30000);
if (!wallet) { const checkProof = setInterval(() => {
localStorage.removeItem(sessionStorageKey); const currentWallet = tonConnectUI.wallet;
if (
const refreshPayload = async () => { currentWallet?.connectItems?.tonProof &&
tonConnectUI.setConnectRequestParameters({ state: "loading" }); !("error" in currentWallet.connectItems.tonProof)
) {
const value = await request clearInterval(checkProof);
.post<{ clearTimeout(timeout);
auth_v1_token: string; resolve(currentWallet.connectItems.tonProof.proof);
}>("/auth.twa", {
twa_data: WebApp.initData,
})
.catch((error: any) => {
console.error("Error in authentication request: ", error);
throw new Error("Failed to authenticate.");
});
if (!value) {
tonConnectUI.setConnectRequestParameters(null);
} else {
tonConnectUI.setConnectRequestParameters({
state: "ready",
value: {
tonProof: value?.data?.auth_v1_token,
},
});
} }
}, 500);
});
}; };
void refreshPayload(); const makeAuthRequest = async (params: {
setInterval(refreshPayload, payloadTTLMS); twa_data: string;
ton_proof?: {
return; account: any;
} ton_proof: any;
};
if ( }) => {
wallet.connectItems?.tonProof && const res = await request.post<{
!("error" in wallet.connectItems.tonProof)
) {
const tonProof = wallet.connectItems.tonProof.proof;
console.log("DEBUG TON-PROOF", tonProof);
request
.post<{
connected_wallet: null | { connected_wallet: null | {
version: string; version: string;
address: string; address: string;
ton_balance: string; ton_balance: string;
}; };
auth_v1_token: string; auth_v1_token: string;
}>("/auth.twa", { }>("/auth.twa", params);
if (res?.data?.auth_v1_token) {
localStorage.setItem(sessionStorageKey, res.data.auth_v1_token);
} else {
throw new Error("Failed to get auth token");
}
return res;
};
return useMutation(["auth"], async () => {
clearInterval(interval.current);
console.log("DEBUG: Starting auth flow");
// Case 1: Not connected - need to connect and get proof
if (!tonConnectUI.connected) {
console.log("DEBUG: No wallet connection, starting flow");
localStorage.removeItem(sessionStorageKey);
const refreshPayload = async () => {
tonConnectUI.setConnectRequestParameters({ state: "loading" });
const value = await request.post<{ auth_v1_token: string }>("/auth.twa", {
twa_data: WebApp.initData,
});
if (value?.data?.auth_v1_token) {
tonConnectUI.setConnectRequestParameters({
state: "ready",
value: { tonProof: value.data.auth_v1_token },
});
} else {
tonConnectUI.setConnectRequestParameters(null);
}
};
await refreshPayload();
interval.current = setInterval(refreshPayload, payloadTTLMS);
const tonProof = await waitForWalletProof();
console.log("DEBUG: Got initial proof", tonProof);
return makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: tonConnectUI.wallet!.account,
ton_proof: tonProof,
},
});
}
// Case 2: Connected with proof - use it
if (wallet?.connectItems?.tonProof && !("error" in wallet.connectItems.tonProof)) {
console.log("DEBUG: Using existing proof");
return makeAuthRequest({
twa_data: WebApp.initData, twa_data: WebApp.initData,
ton_proof: { ton_proof: {
account: wallet.account, account: wallet.account,
ton_proof: tonProof, ton_proof: wallet.connectItems.tonProof.proof,
}, },
})
.then((res) => {
if (res?.data?.auth_v1_token) {
localStorage.setItem(sessionStorageKey, res?.data?.auth_v1_token);
} else {
alert("Please try another wallet");
}
})
.catch((error: any) => {
console.error("Error in authentication request: ", error);
throw new Error("Failed to authenticate.");
}); });
} else {
void tonConnectUI.disconnect();
localStorage.removeItem(sessionStorageKey)
} }
// Case 3: Connected without proof - already authenticated
console.log("DEBUG: Connected without proof, proceeding without it");
return makeAuthRequest({
twa_data: WebApp.initData,
});
}); });
}; };

View File

@ -77,7 +77,7 @@ export const useRootStore = create<RootStore>((set) => ({
authors: [], authors: [],
setAuthors: (authors) => set({ authors }), setAuthors: (authors) => set({ authors }),
royalty: [{ address: "", value: 100 }], royalty: [],
setRoyalty: (royalty) => set({ royalty }), setRoyalty: (royalty) => set({ royalty }),
price: 0.15, price: 0.15,