"use client"; import { PhoneNumberUtil } from "google-libphonenumber"; import { resolveCountryName } from "@/data/countries"; export type UserGeoRegion = { ip?: string; city?: string; country?: string; countryCode?: string; // e.g. "IR", "US", "GB" phoneCode?: string; // e.g. "+98", "+1", "+44" }; const phoneUtil = PhoneNumberUtil.getInstance(); const STORAGE_KEY = "user_geo_region"; const PHONE_STORAGE_KEY = "geoIPPhoneCode"; let cachedRegion: UserGeoRegion | null = null; let geoRegionPromise: Promise | null = null; const listeners = new Set<(region: UserGeoRegion) => void>(); export function resetUserGeoRegionForTesting() { cachedRegion = null; geoRegionPromise = null; listeners.clear(); } export function subscribeToUserGeoRegion( fn: (region: UserGeoRegion) => void, ): () => void { listeners.add(fn); return () => { listeners.delete(fn); }; } export function getStoredUserGeoRegion(): UserGeoRegion | null { if (cachedRegion) return cachedRegion; if (typeof window !== "undefined") { try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { const parsed = JSON.parse(stored) as UserGeoRegion; if ( parsed && (parsed.country || parsed.phoneCode || parsed.city || parsed.countryCode) ) { cachedRegion = parsed; return parsed; } } } catch {} } return null; } export function setStoredUserGeoRegion(region: UserGeoRegion) { const current = cachedRegion || (typeof window !== "undefined" ? getStoredUserGeoRegion() : null); const merged: UserGeoRegion = { ...(current || {}), ...region, }; cachedRegion = merged; if (typeof window !== "undefined") { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(merged)); if (merged.phoneCode) { localStorage.setItem(PHONE_STORAGE_KEY, merged.phoneCode); } } catch {} } listeners.forEach((fn) => { fn(merged); }); } function resolvePhoneCodeFromCountryCode(isoCode?: string): string | undefined { if (!isoCode) return undefined; try { const callingCode = phoneUtil.getCountryCodeForRegion( isoCode.toUpperCase(), ); if (callingCode) { return `+${callingCode}`; } } catch {} return undefined; } function getFallbackGeoRegion(): UserGeoRegion { const existing = getStoredUserGeoRegion(); if ( existing && (existing.country || existing.phoneCode || existing.countryCode || existing.city) ) { console.log( "[GEO_BRIDGE_LOG] 📦 Preserving existing stored user geo region:", JSON.stringify(existing), ); return existing; } console.log("[GEO_BRIDGE_LOG] ⚠️ Using default fallback region (+44)..."); const defaultRegion: UserGeoRegion = { phoneCode: "+44", }; setStoredUserGeoRegion(defaultRegion); return defaultRegion; } /** * Fetch geo region strictly via Flutter bridge action protocol ('get_location'). * No direct backend HTTP requests are made. */ function fetchFlutterBridgeGeoRegion(): Promise { return new Promise((resolve) => { let resolved = false; let unsubscribe: (() => void) | undefined; let timer: ReturnType | null = null; const finish = (region: UserGeoRegion) => { if (resolved) return; resolved = true; if (timer) { clearTimeout(timer); timer = null; } if (unsubscribe) { try { unsubscribe(); } catch {} unsubscribe = undefined; } resolve(region); }; console.log( "[GEO_BRIDGE_LOG] 🌉 Registering Flutter response listener for get_location...", ); // 1) Register a listener with window.addFlutterResponseListener if ( typeof window !== "undefined" && typeof window.addFlutterResponseListener === "function" ) { unsubscribe = window.addFlutterResponseListener( (event: FlutterResponseEvent) => { if (!event) return; const action = event.action?.toLowerCase(); // 2) Listens for event.action === "get_location" if (action === "get_location") { console.log( "[GEO_BRIDGE_LOG] 📥 Flutter get_location response received:", JSON.stringify(event), ); const data = (event.data || (event as any).payload) as | { ip?: string; country?: string; country_code?: string; city?: string; } | undefined; if ( event.success && data && (data.country || data.country_code || data.city || data.ip) ) { const rawCountry = data.country || data.country_code || ""; const isoCode = data.country_code || (rawCountry && rawCountry.trim().length === 2 ? rawCountry.trim().toUpperCase() : undefined); const phoneCode = resolvePhoneCodeFromCountryCode(isoCode); const countryName = resolveCountryName(data.country || isoCode, "en") || data.country; const countryFa = resolveCountryName(data.country || isoCode, "fa") || countryName; console.log( `[GEO_BRIDGE_LOG] 🌍 Resolved from Flutter bridge: FA=${countryFa} | EN=${countryName} | Code=${isoCode} | City=${data.city || "None"} | IP=${data.ip || "None"} | Phone=${phoneCode || "+44"}`, ); const region: UserGeoRegion = { ip: data.ip, city: data.city, country: countryName, countryCode: isoCode, phoneCode: phoneCode || "+44", }; setStoredUserGeoRegion(region); finish(region); } else { console.warn( "[GEO_BRIDGE_LOG] ⚠️ Flutter get_location unsuccessful or empty, falling back to local cached default", ); finish(getFallbackGeoRegion()); } } }, ); } // 3) Posts message: window.HabibApp.postMessage(JSON.stringify({ action: "get_location" })) try { if (typeof window !== "undefined" && window.HabibApp?.postMessage) { console.log( "[GEO_BRIDGE_LOG] 📤 Posting { action: 'get_location' } to window.HabibApp...", ); window.HabibApp.postMessage(JSON.stringify({ action: "get_location" })); } else if ( typeof window !== "undefined" && typeof (window as any).sendToFlutter === "function" ) { console.log( "[GEO_BRIDGE_LOG] 📤 Sending get_location via sendToFlutter...", ); (window as any).sendToFlutter("get_location"); } } catch (e) { console.error( "[GEO_BRIDGE_LOG] ❌ Failed to post get_location message:", e, ); } // 4) Has a timeout fallback of 4000ms: if no response from Flutter, fallback to cached / default region (no HTTP) timer = setTimeout(() => { if (!resolved) { console.warn( "[GEO_BRIDGE_LOG] ⏱️ Flutter bridge get_location timed out after 4000ms, using fallback region...", ); finish(getFallbackGeoRegion()); } }, 4000); }); } /** * Get user geo region. * Uses Flutter bridge action ('get_location') exclusively for location detection. * Never performs direct HTTP requests. */ export function getUserGeoRegion(force = false): Promise { // If !force: Check cachedRegion or getStoredUserGeoRegion(). If present and has location, return it immediately. if (!force) { const existing = cachedRegion || getStoredUserGeoRegion(); if ( existing && (existing.country || existing.countryCode || existing.city) ) { console.log( "[GEO_BRIDGE_LOG] 📦 Returning cached/stored user geo region:", JSON.stringify(existing), ); return Promise.resolve(existing); } } else { cachedRegion = null; geoRegionPromise = null; } if (geoRegionPromise) { return geoRegionPromise; } // Check if running inside Flutter Webview: typeof window !== "undefined" && window.HabibApp?.postMessage const isFlutter = typeof window !== "undefined" && (Boolean(window.HabibApp?.postMessage) || typeof (window as any).sendToFlutter === "function"); if (isFlutter) { console.log( "[GEO_BRIDGE_LOG] 📱 Inside Flutter WebView detected, requesting location via Flutter bridge action 'get_location'", ); geoRegionPromise = fetchFlutterBridgeGeoRegion().finally(() => { geoRegionPromise = null; }); } else { console.log( "[GEO_BRIDGE_LOG] 🌐 Standard browser / non-Flutter environment: resolving from cached storage or default fallback (no HTTP calls)", ); const fallback = getFallbackGeoRegion(); geoRegionPromise = Promise.resolve(fallback); } return geoRegionPromise; }