Skip to content
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

Shayan/FEQ-2696/Fix redundant Cloudflare trace API calls #91

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 29 additions & 23 deletions src/utils/__test__/country.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
import { describe, beforeEach, afterEach, it, expect, vi } from "vitest";
import { describe, beforeEach, it, expect, vi, Mock } from "vitest";
import { getCountry } from "../country.utils";
import Cookies from "js-cookie";

vi.mock("js-cookie");
global.fetch = vi.fn();

describe("getCountry", () => {
beforeEach(() => {
global.fetch = vi.fn();
});

afterEach(() => {
vi.resetAllMocks();
vi.clearAllMocks();
vi.resetModules();
});

it("should return the country code in lowercase when available", async () => {
// Mock fetch response
(global.fetch as vi.Mock).mockResolvedValue({
text: async () => "loc=US\nother=info\n",
it("should return country code from Cloudflare trace", async () => {
(global.fetch as Mock).mockResolvedValueOnce({
text: () => Promise.resolve("loc=US\nother=value"),
});

const country = await getCountry();
expect(country).toBe("us");
const result = await getCountry();
expect(result).toBe("us");
});

it("should return an empty string if the loc field is not present", async () => {
(global.fetch as vi.Mock).mockResolvedValue({
text: async () => "other=info\n",
});
it("should return country code from cookie when Cloudflare fails", async () => {
vi.clearAllMocks();
vi.resetModules();

const country = await getCountry();
expect(country).toBe("");
});
(global.fetch as Mock).mockRejectedValueOnce(new Error("Network error"));
(Cookies.get as Mock).mockReturnValue(JSON.stringify({ clients_country: "fr" }));

it("should return an empty string if the fetch fails", async () => {
(global.fetch as vi.Mock).mockRejectedValue(new Error("Fetch failed"));
const { getCountry } = await import("../country.utils");
const result = await getCountry();
expect(result).toBe("fr");
});

const country = await getCountry();
expect(country).toBe("");
it("should return empty string if no country data is available", async () => {
vi.clearAllMocks();
vi.resetModules();
(global.fetch as Mock).mockRejectedValueOnce(new Error("Network error"));
(Cookies.get as Mock).mockReturnValue(JSON.stringify({}));
const { getCountry } = await import("../country.utils");
const result = await getCountry();
expect(result).toBe("");
});
});
31 changes: 21 additions & 10 deletions src/utils/country.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { cloudflareTrace } from "../constants/url.constants";
type TraceData = {
loc?: string;
};
let countryPromise: Promise<string> | null = null;

/**
* Fetches the country information based on Cloudflare's trace data or a fallback from cookies.
Expand All @@ -19,14 +20,24 @@ type TraceData = {
*/

export const getCountry = async (): Promise<string> => {
try {
const response = await fetch(cloudflareTrace).catch(() => null);
if (!response) return "";
const text = await response.text().catch(() => "");
const entries = text ? text.split("\n").map((v) => v.split("=", 2)) : [];
const data: TraceData = entries.length ? Object.fromEntries(entries) : {};
return data.loc?.toLowerCase() || JSON.parse(Cookies.get("website_status") || "{}")?.loc || "";
} catch (error) {
return "";
}
if (countryPromise) return countryPromise;

const cookieCountry = JSON.parse(Cookies.get("website_status") || "{}")?.clients_country;

countryPromise = (async () => {
try {
const response = await fetch(cloudflareTrace).catch(() => null);
if (!response) return cookieCountry || "";

const text = await response.text().catch(() => "");
if (!text) return cookieCountry || "";

const data: TraceData = Object.fromEntries(text.split("\n").map((v) => v.split("=", 2)));
return data.loc?.toLowerCase() || cookieCountry || "";
} catch {
return cookieCountry || "";
}
})();

return countryPromise;
};