Browse Source

fix(navigation): implement synchronous hardware back handler to prevent accidental webview exit on section back

master
mortezaei 2 days ago
parent
commit
b1e75845f1
  1. 5
      src/app/layout.tsx
  2. 8
      src/app/questions-list/questions-list-client.tsx
  3. 11
      src/components/Componentes/hardware-back-bridge.tsx
  4. 25
      src/hooks/use-hardware-back-handler.ts
  5. 4
      src/types/window.d.ts

5
src/app/layout.tsx

@ -320,6 +320,11 @@ export default async function RootLayout({
return Promise.resolve({ handled: false });
};
}
if (!window.__habibHandleHardwareBackSync) {
window.__habibHandleHardwareBackSync = function() {
return false;
};
}
})();
`,
}}

8
src/app/questions-list/questions-list-client.tsx

@ -152,15 +152,15 @@ export default function QuestionsListClient() {
}, []);
const handleCloseSection = useCallback(() => {
setActiveSectionSlug(null);
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search);
if (params.get("section")) {
setActiveSectionSlug(null);
window.history.back();
return;
const url = new URL(window.location.href);
url.searchParams.delete("section");
window.history.replaceState({}, "", url.toString());
}
}
setActiveSectionSlug(null);
}, []);
const questionListItems = useMemo(

11
src/components/Componentes/hardware-back-bridge.tsx

@ -1,11 +1,14 @@
"use client";
import { useEffect } from "react";
import { handleHardwareBack } from "@/hooks/use-hardware-back-handler";
import {
handleHardwareBack,
handleHardwareBackSync,
} from "@/hooks/use-hardware-back-handler";
/**
* Wires the React hardware-back handler stack to the global
* window.__habibHandleHardwareBack function.
* window.__habibHandleHardwareBack and window.__habibHandleHardwareBackSync functions.
*
* Mount this once in the Providers tree (after React hydration).
* It replaces the bootstrap stub with the real handler that walks
@ -14,11 +17,13 @@ import { handleHardwareBack } from "@/hooks/use-hardware-back-handler";
export function HardwareBackBridge() {
useEffect(() => {
window.__habibHandleHardwareBack = handleHardwareBack;
window.__habibHandleHardwareBackSync = handleHardwareBackSync;
return () => {
// On unmount (shouldn't happen in practice), restore the stub.
// On unmount (shouldn't happen in practice), restore the stubs.
window.__habibHandleHardwareBack = () =>
Promise.resolve({ handled: false });
window.__habibHandleHardwareBackSync = () => false;
};
}, []);

25
src/hooks/use-hardware-back-handler.ts

@ -60,6 +60,29 @@ export function useHardwareBackHandler(
}, [enabled]);
}
/**
* Synchronously invokes the topmost hardware-back handler if registered.
* Returns true if a handler consumed the event, or false if Flutter should handle/close.
*/
export function handleHardwareBackSync(): boolean {
if (backHandlerStack.length === 0) {
return false;
}
const handler = backHandlerStack[backHandlerStack.length - 1];
try {
const result = handler();
if (result instanceof Promise) {
// Promise was initiated by invoking the handler; it is handled in JS!
return true;
}
return Boolean(result);
} catch (error) {
console.warn("[HardwareBackSync] Handler threw:", error);
return false;
}
}
/**
* Called by the root bootstrap script when Flutter sends a hardware back event.
* Returns { handled: true } if a web handler consumed the event, or
@ -74,7 +97,7 @@ export async function handleHardwareBack(): Promise<{ handled: boolean }> {
const handler = backHandlerStack[backHandlerStack.length - 1];
try {
const result = await handler();
return { handled: result };
return { handled: Boolean(result) };
} catch (error) {
console.warn("[HardwareBack] Handler threw:", error);
return { handled: false };

4
src/types/window.d.ts

@ -100,6 +100,10 @@ declare global {
* or { handled: false } if Flutter should close the WebView screen.
*/
__habibHandleHardwareBack?: () => Promise<{ handled: boolean }>;
/**
* Synchronous variant called by Flutter for instant response without Promise serialization issues.
*/
__habibHandleHardwareBackSync?: () => boolean;
/**
* Unique ID per document load. If this changes on back navigation,
* it proves a hard reload / WebView recreation happened.

Loading…
Cancel
Save