Browse Source

feat: enhance authentication flow and improve request handling in Intro and AuthBridge

master
mortezaei 2 months ago
parent
commit
f9417728cf
  1. 48
      src/app/intro/page.tsx
  2. 66
      src/lib/auth-bridge.ts
  3. 4
      src/lib/http.ts

48
src/app/intro/page.tsx

@ -2,24 +2,24 @@
import Image from "next/image"; import Image from "next/image";
import { useState } from "react"; import { useState } from "react";
import { useRouter } from "next/navigation";
import Button from "@/components/ui/button"; import Button from "@/components/ui/button";
import NavigationButton from "@/components/ui/navigation-button"; import NavigationButton from "@/components/ui/navigation-button";
import ReportActionsSheet from "@/components/ui/report-actions-sheet"; import ReportActionsSheet from "@/components/ui/report-actions-sheet";
import { authBridge } from "@/lib/auth-bridge";
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
import { localizePath } from "@/i18n/config"; import { localizePath } from "@/i18n/config";
import { useI18n } from "@/i18n/provider"; import { useI18n } from "@/i18n/provider";
export default function Intro() {
const { dictionary: t, locale } = useI18n();
const { data: profile } = useMarriageProfileQuery();
const [isReportSheetOpen, setIsReportSheetOpen] = useState(false);
function getSubmitPath(profile: ReturnType<typeof useMarriageProfileQuery>["data"]) {
const isInCase = profile?.status === "in_case"; const isInCase = profile?.status === "in_case";
const isFemaleAcceptedFlow = const isFemaleAcceptedFlow =
isInCase && isInCase &&
(profile?.active_case?.status === "payment_pending" || (profile?.active_case?.status === "payment_pending" ||
profile?.active_case?.status === "female_accepted" || profile?.active_case?.status === "female_accepted" ||
profile?.active_case?.status === "payment_done"); profile?.active_case?.status === "payment_done");
const submitPath = isFemaleAcceptedFlow
return isFemaleAcceptedFlow
? "/request-accepted" ? "/request-accepted"
: isInCase && profile?.active_case?.status === "male_accepted" : isInCase && profile?.active_case?.status === "male_accepted"
? "/request-sent" ? "/request-sent"
@ -32,7 +32,39 @@ export default function Intro() {
: profile?.status === "in_case" || profile?.status === "matched" : profile?.status === "in_case" || profile?.status === "matched"
? "/new-match" ? "/new-match"
: "/terms"; : "/terms";
const submitHref = localizePath(submitPath, locale);
}
export default function Intro() {
const router = useRouter();
const { dictionary: t, locale } = useI18n();
const { data: profile, refetch } = useMarriageProfileQuery({
enabled: authBridge.isAuthenticated(),
});
const [isReportSheetOpen, setIsReportSheetOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async () => {
if (isSubmitting) {
return;
}
setIsSubmitting(true);
try {
if (!authBridge.isAuthenticated()) {
const token = await authBridge.ensureToken();
if (!token) {
return;
}
}
const profileResponse = profile ?? (await refetch()).data;
const nextPath = localizePath(getSubmitPath(profileResponse), locale);
router.push(nextPath);
} finally {
setIsSubmitting(false);
}
};
return ( return (
<div className="pt-7"> <div className="pt-7">
@ -123,7 +155,9 @@ export default function Intro() {
/> />
</div> </div>
<div className="mt-7 pb-5"> <div className="mt-7 pb-5">
<Button href={submitHref}>{t.common.submit}</Button>
<Button onClick={handleSubmit} disabled={isSubmitting}>
{t.common.submit}
</Button>
</div> </div>
</main> </main>
</div> </div>

66
src/lib/auth-bridge.ts

@ -26,7 +26,6 @@ class AuthBridge {
private readyCallbacks: Array<() => void> = []; private readyCallbacks: Array<() => void> = [];
private pendingResolvers: Array<(token: string | null) => void> = []; private pendingResolvers: Array<(token: string | null) => void> = [];
private flutterResponseUnsubscribe?: () => void; private flutterResponseUnsubscribe?: () => void;
private loginTimeoutId?: number;
constructor() { constructor() {
this.init(); this.init();
@ -70,45 +69,63 @@ class AuthBridge {
) => () => void; ) => () => void;
}; };
if (typeof win.addFlutterResponseListener === "function") {
const attach = () => {
if (typeof win.addFlutterResponseListener !== "function") {
return false;
}
this.flutterResponseUnsubscribe = win.addFlutterResponseListener((event) => { this.flutterResponseUnsubscribe = win.addFlutterResponseListener((event) => {
if (event.action === "login") { if (event.action === "login") {
this.handleLoginResponse(event.success); this.handleLoginResponse(event.success);
} }
}); });
return true;
};
if (attach()) {
return; return;
} }
const fallbackInterval = window.setInterval(() => { const fallbackInterval = window.setInterval(() => {
if (typeof win.addFlutterResponseListener === "function") {
if (attach()) {
window.clearInterval(fallbackInterval); window.clearInterval(fallbackInterval);
this.flutterResponseUnsubscribe = win.addFlutterResponseListener((event) => {
if (event.action === "login") {
this.handleLoginResponse(event.success);
}
});
} }
}, 50); }, 50);
} }
private handleLoginResponse(success: boolean) { private handleLoginResponse(success: boolean) {
if (success && this.syncFromStorage()) {
if (success) {
if (this.syncFromStorage()) {
this.loginRequested = false; this.loginRequested = false;
this.markReady(this.token); this.markReady(this.token);
return; return;
} }
if (!success) {
window.setTimeout(() => {
if (this.syncFromStorage()) {
this.loginRequested = false;
this.markReady(this.token);
return;
}
console.warn("⚠️ Flutter login response arrived before token");
this.loginRequested = false; this.loginRequested = false;
console.warn("⚠️ Flutter login failed");
this.resolvePending(null); this.resolvePending(null);
this.markReady(null); this.markReady(null);
}, 50);
return;
} }
this.loginRequested = false;
console.warn("⚠️ Flutter login failed");
this.resolvePending(null);
this.markReady(null);
} }
private requestLogin() { private requestLogin() {
if (this.loginRequested || this.token) { if (this.loginRequested || this.token) {
return;
return true;
} }
this.loginRequested = true; this.loginRequested = true;
@ -116,28 +133,21 @@ class AuthBridge {
if (!sent) { if (!sent) {
this.loginRequested = false; this.loginRequested = false;
return;
}
if (this.loginTimeoutId) {
window.clearTimeout(this.loginTimeoutId);
return false;
} }
this.loginTimeoutId = window.setTimeout(() => {
window.setTimeout(() => {
if (!this.token) { if (!this.token) {
console.warn("⚠️ Flutter login timeout"); console.warn("⚠️ Flutter login timeout");
this.loginRequested = false; this.loginRequested = false;
this.resolvePending(null); this.resolvePending(null);
} }
}, LOGIN_TIMEOUT_MS); }, LOGIN_TIMEOUT_MS);
}
private markReady(token: string | null) {
if (this.loginTimeoutId) {
window.clearTimeout(this.loginTimeoutId);
this.loginTimeoutId = undefined;
return true;
} }
private markReady(token: string | null) {
this.isReady = true; this.isReady = true;
this.resolvePending(token); this.resolvePending(token);
this.notifyReady(); this.notifyReady();
@ -162,13 +172,15 @@ class AuthBridge {
return; return;
} }
this.setupFlutterResponseListener();
if (this.syncFromStorage()) { if (this.syncFromStorage()) {
this.markReady(this.token); this.markReady(this.token);
return; return;
} }
this.setupFlutterResponseListener();
this.requestLogin();
this.isReady = true;
this.notifyReady();
} }
public onReady(callback: () => void) { public onReady(callback: () => void) {
@ -190,10 +202,12 @@ class AuthBridge {
} }
this.setupFlutterResponseListener(); this.setupFlutterResponseListener();
this.requestLogin();
return await new Promise<string | null>((resolve) => { return await new Promise<string | null>((resolve) => {
this.pendingResolvers.push(resolve); this.pendingResolvers.push(resolve);
if (!this.requestLogin()) {
this.resolvePending(null);
}
}); });
} }

4
src/lib/http.ts

@ -60,13 +60,13 @@ export const http = axios.create({
withCredentials: true, withCredentials: true,
}); });
http.interceptors.request.use(async (config) => {
http.interceptors.request.use((config) => {
if (shouldUseProxy() && config.url && !isAbsoluteUrl(config.url)) { if (shouldUseProxy() && config.url && !isAbsoluteUrl(config.url)) {
config.params = withProxyPathParam(config.params, config.url); config.params = withProxyPathParam(config.params, config.url);
config.url = ""; config.url = "";
} }
const token = await authBridge.ensureToken();
const token = authBridge.getToken() ?? getClientCookie("HABIB_TOKEN");
if (token) { if (token) {
config.headers.Authorization = `Token ${token}`; config.headers.Authorization = `Token ${token}`;

Loading…
Cancel
Save