Skip to content

Commit

Permalink
Merge pull request #91 from deriv-com/shayan/FEQ-2696/fix-network-fai…
Browse files Browse the repository at this point in the history
…ling-on-cloudflare-cgi-trace-request

Shayan/FEQ-2696/Fix redundant Cloudflare trace API calls
  • Loading branch information
shayan-deriv authored Nov 19, 2024
2 parents 4ffc491 + a8de1d2 commit ab1904f
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 33 deletions.
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;
};

0 comments on commit ab1904f

Please sign in to comment.