-
Notifications
You must be signed in to change notification settings - Fork 146
Switch to ipinfo.io-based country detection fixing Issue 2448 #2605
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1fc409d
284de95
1c38117
dc30a6d
593ba27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,31 +1,27 @@ | ||
| import { useState, useEffect } from "react"; | ||
| import { Navigate } from "react-router-dom"; | ||
| import { getCountryId } from "../utils/ipinfoCountry.js"; | ||
|
|
||
| /** | ||
| * Redirects the user to their country-specific route based on their IP address. | ||
| */ | ||
| export default function RedirectToCountry() { | ||
| // Find country ID | ||
| const countryId = findCountryId(); | ||
| const [countryId, setCountryId] = useState(null); | ||
|
|
||
| return <Navigate to={`/${countryId}`} replace />; | ||
| } | ||
| useEffect(() => { | ||
| const controller = new AbortController(); | ||
| const { signal } = controller; | ||
|
|
||
| /** | ||
| * Based on the URL and user's browser, determine country ID; | ||
| * if not possible, return "us" as country ID | ||
| * @returns {String} | ||
| */ | ||
| export function findCountryId() { | ||
| const COUNTRY_CODES = { | ||
| "en-US": "us", | ||
| "en-GB": "uk", | ||
| "en-CA": "ca", | ||
| "en-NG": "ng", | ||
| "en-IL": "il", | ||
| }; | ||
| const fetchCountry = async () => { | ||
| const id = await getCountryId(signal); | ||
| setCountryId(id); | ||
| }; | ||
|
|
||
| const browserLanguage = navigator.language; | ||
| fetchCountry(); | ||
| return () => controller.abort(); | ||
| }, []); | ||
|
|
||
| if (Object.keys(COUNTRY_CODES).includes(browserLanguage)) { | ||
| return COUNTRY_CODES[browserLanguage]; | ||
| } else { | ||
| return "us"; | ||
| } | ||
| if (countryId === null) return null; | ||
|
|
||
| return <Navigate to={`/${countryId}`} replace />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| /** | ||
| * @file utils/ipinfoCountry.js | ||
| * | ||
| * Determines the PolicyEngine country segment ("uk", "us", …) for the user | ||
| * via: | ||
| * | ||
| * IP geolocation or Browser language and defaults to "us" | ||
| * | ||
| * @typedef {"uk" | "us" | "ca" | "ng" | "il"} CountryId | ||
| */ | ||
|
|
||
| /** | ||
| * Maps ISO codes to country-id. | ||
| */ | ||
| const ISO2_TO_SEGMENT = { | ||
| gb: "uk", | ||
| im: "uk", // Isle of Man | ||
| je: "uk", // Jersey | ||
| gg: "uk", // Guernsey | ||
| us: "us", | ||
| ca: "ca", | ||
| ng: "ng", | ||
| il: "il", | ||
| }; | ||
|
|
||
| /** | ||
| * Maps navigator.language strings to country-id. | ||
| */ | ||
| const LANGUAGE_TO_SEGMENT = { | ||
| "en-GB": "uk", | ||
| "en-US": "us", | ||
| "en-CA": "ca", | ||
| "en-NG": "ng", | ||
| "en-IL": "il", | ||
| }; | ||
|
|
||
| /** | ||
| * Attempt to get a 'CountryId' from the client's public IP via ipinfo.io. | ||
| * | ||
| * @param {AbortSignal} [signal] | ||
| * @returns {Promise<CountryId | null>} | ||
| */ | ||
| export async function resolveCountryFromIp(signal) { | ||
| const token = process.env.REACT_APP_IPINFO_TOKEN; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue, blocking: Please reopen this PR off of a branch, not a fork As a fork contributor, I believe you don't have access to this environment variable, and I'm sorry that I forgot to advise you on this. Could you reopen this PR off of a branch? Let me know if you need additional permissions to do so. |
||
| if (!token) { | ||
| console.warn("Missing ipinfo token – skipping IP lookup"); | ||
| return null; | ||
| } | ||
|
|
||
| const endpoint = `https://api.ipinfo.io/lite/me?token=${token}`; | ||
| const resp = await fetch(endpoint, { signal }); | ||
| if (!resp.ok) throw new Error(`ipinfo returned ${resp.status}`); | ||
|
|
||
| const { country_code: iso2 = "" } = await resp.json(); | ||
| return ISO2_TO_SEGMENT[iso2.toLowerCase()] ?? null; | ||
| } | ||
|
|
||
| /** | ||
| * Get country-id from browser language. | ||
| * | ||
| * @returns {CountryId | null} | ||
| */ | ||
| export function resolveCountryFromLanguage() { | ||
| return LANGUAGE_TO_SEGMENT[navigator.language] ?? null; | ||
| } | ||
|
|
||
| /** | ||
| * Get country id by IP or browser language and default to "us". | ||
| * | ||
| * @param {AbortSignal} [signal] | ||
| * @returns {Promise<CountryId>} | ||
| */ | ||
| export async function getCountryId(signal) { | ||
| try { | ||
| const countryCode_Ip = await resolveCountryFromIp(signal); | ||
| if (countryCode_Ip) return countryCode_Ip; | ||
| } catch (err) { | ||
| if (signal?.aborted) throw err; | ||
| console.error("IP‑based country lookup failed:", err); | ||
| } | ||
|
|
||
| // Using browser language | ||
| const browserLang = resolveCountryFromLanguage(); | ||
| if (browserLang) return browserLang; | ||
|
|
||
| //default | ||
| return "us"; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.