diff --git a/.vscode/.debug.script.mjs b/.vscode/.debug.script.mjs index 9ca93363c..ecdda576d 100644 --- a/.vscode/.debug.script.mjs +++ b/.vscode/.debug.script.mjs @@ -14,10 +14,22 @@ fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n')) // bootstrap spawn( // TODO: terminate `npm run dev` when Debug exits. - process.platform === 'win32' ? 'npm.cmd' : 'npm', + 'npm', ['run', 'dev'], { + shell: true, stdio: 'inherit', - env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }), + // On Windows, Node's spawn can throw EINVAL if the env object contains + // special keys that start with '=' (e.g. '=C:'). Filter those out. + env: (() => { + const env = {} + for (const [key, val] of Object.entries(process.env)) { + if (!key || key.startsWith('=') || key.includes('\0')) continue + if (typeof val !== 'string' || val.includes('\0')) continue + env[key] = val + } + env.VSCODE_DEBUG = 'true' + return env + })(), }, ) \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index a6bfb57c0..9d348e776 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -15,6 +15,7 @@ import { proxyFetchPost } from '@/api/http'; import { hasStackKeys } from '@/lib'; import { useTranslation } from 'react-i18next'; import WindowControls from '@/components/WindowControls'; +import { loginByStackWithAutoCreate } from '@/service/stackAuthApi'; const HAS_STACK_KEYS = hasStackKeys(); let lock = false; @@ -135,7 +136,19 @@ export default function Login() { return; } - setAuth({ email: formData.email, ...data }); + const token = (data as any)?.token as string | undefined; + const email = ((data as any)?.email as string | undefined) ?? formData.email; + if (!token) { + setGeneralError(t('layout.login-failed-please-try-again')); + return; + } + + setAuth({ + token, + email, + username: (data as any)?.username ?? null, + user_id: (data as any)?.user_id ?? null, + }); setModelType('cloud'); // Record VITE_USE_LOCAL_PROXY value at login const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null; @@ -153,9 +166,9 @@ export default function Login() { const handleLoginByStack = async (token: string) => { try { - const data = await proxyFetchPost('/api/login-by_stack?token=' + token, { - token: token, - }); + // 1) Try normal login (existing profile) + // 2) If not found, auto-create profile via signup and continue + const data = await loginByStackWithAutoCreate(token); const errorMessage = getLoginErrorMessage(data); if (errorMessage) { @@ -164,7 +177,20 @@ export default function Login() { } console.log('data', data); setModelType('cloud'); - setAuth({ email: formData.email, ...data }); + + const authToken = (data as any)?.token as string | undefined; + const email = ((data as any)?.email as string | undefined) ?? formData.email; + if (!authToken) { + setGeneralError(t('layout.login-failed-please-try-again')); + return; + } + + setAuth({ + token: authToken, + email, + username: (data as any)?.username ?? null, + user_id: (data as any)?.user_id ?? null, + }); // Record VITE_USE_LOCAL_PROXY value at login const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null; setLocalProxyValue(localProxyValue); @@ -181,6 +207,11 @@ export default function Login() { const handleReloadBtn = async (type: string) => { if (!app) { + // Keep the buttons visible so users discover the option, but make it + // clear when Stack OAuth isn't configured for local builds. + setGeneralError( + 'Social sign-in is not configured for this build. Set VITE_STACK_PROJECT_ID, VITE_STACK_PUBLISHABLE_CLIENT_KEY, and VITE_STACK_SECRET_SERVER_KEY.' + ); console.error('Stack app not initialized'); return; } @@ -245,7 +276,12 @@ export default function Login() { lock = true; setIsLoading(true); - let accessToken = await handleGetToken(code); + const accessToken = await handleGetToken(code); + if (!accessToken) { + setGeneralError(t('layout.login-failed-please-try-again')); + setIsLoading(false); + return; + } handleLoginByStack(accessToken); setTimeout(() => { lock = false; @@ -360,39 +396,31 @@ export default function Login() { {t('layout.sign-up')} - {HAS_STACK_KEYS && ( -
diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx
index 0f589a631..0fb4b25df 100644
--- a/src/pages/SignUp.tsx
+++ b/src/pages/SignUp.tsx
@@ -14,6 +14,7 @@ import eyeOff from "@/assets/eye-off.svg";
import { proxyFetchPost } from "@/api/http";
import { hasStackKeys } from "@/lib";
import { useTranslation } from "react-i18next";
+import { loginByStackToken } from "@/service/stackAuthApi";
const HAS_STACK_KEYS = hasStackKeys();
let lock = false;
@@ -129,9 +130,15 @@ export default function SignUp() {
const handleLoginByStack = async (token: string) => {
try {
- const data = await proxyFetchPost("/api/login-by_stack?token=" + token, {
- token: token,
- invite_code: localStorage.getItem("invite_code") || "",
+ if (!token) {
+ setGeneralError(t("layout.login-failed-please-try-again"));
+ return;
+ }
+ const inviteCode = localStorage.getItem("invite_code") || "";
+ const data = await loginByStackToken({
+ token,
+ type: "signup",
+ inviteCode: inviteCode || undefined,
});
if (data.code === 10) {
@@ -139,7 +146,20 @@ export default function SignUp() {
return;
}
console.log("data", data);
- setAuth({ email: formData.email, ...data });
+
+ const authToken = (data as any)?.token as string | undefined;
+ const email = ((data as any)?.email as string | undefined) ?? formData.email;
+ if (!authToken) {
+ setGeneralError(t("layout.login-failed-please-try-again"));
+ return;
+ }
+
+ setAuth({
+ token: authToken,
+ email,
+ username: (data as any)?.username ?? null,
+ user_id: (data as any)?.user_id ?? null,
+ });
navigate("/");
} catch (error: any) {
console.error("Login failed:", error);
@@ -213,6 +233,11 @@ export default function SignUp() {
lock = true;
setIsLoading(true);
const accessToken = await handleGetToken(code);
+ if (!accessToken) {
+ setGeneralError(t("layout.login-failed-please-try-again"));
+ setIsLoading(false);
+ return;
+ }
await handleLoginByStack(accessToken);
setTimeout(() => {
lock = false;
diff --git a/src/service/stackAuthApi.ts b/src/service/stackAuthApi.ts
new file mode 100644
index 000000000..3f40b57f1
--- /dev/null
+++ b/src/service/stackAuthApi.ts
@@ -0,0 +1,45 @@
+import { proxyFetchPost } from '@/api/http'
+
+export type StackAuthFlowType = 'login' | 'signup'
+
+type StackLoginResponse = {
+ code?: number
+ text?: string
+ [key: string]: any
+}
+
+function isUserNotFoundResponse(res: StackLoginResponse | null | undefined): boolean {
+ if (!res || typeof res !== 'object') return false
+ if (res.code !== 1) return false
+ const text = String(res.text ?? '').toLowerCase()
+ return text.includes('user not found')
+}
+
+export async function loginByStackToken(params: {
+ token: string
+ type: StackAuthFlowType
+ inviteCode?: string
+}): Promise