From 0f39e698a758ea8a94dbe5292a9dfe39709ebb1a Mon Sep 17 00:00:00 2001 From: shayan khaleghparast Date: Fri, 15 Nov 2024 16:23:38 +0800 Subject: [PATCH 1/8] refactor: caching the response as a promise --- src/utils/country.utils.ts | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/utils/country.utils.ts b/src/utils/country.utils.ts index d317aa0..3d68263 100644 --- a/src/utils/country.utils.ts +++ b/src/utils/country.utils.ts @@ -4,6 +4,7 @@ import { cloudflareTrace } from "../constants/url.constants"; type TraceData = { loc?: string; }; +let countryPromise: Promise | null = null; /** * Fetches the country information based on Cloudflare's trace data or a fallback from cookies. @@ -19,14 +20,27 @@ type TraceData = { */ export const getCountry = async (): Promise => { - 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; } + + countryPromise = (async () => { + try { + const response = await fetch(cloudflareTrace).catch(() => null); + if (!response) { + return JSON.parse(Cookies.get("website_status") || "{}")?.loc?.toLowerCase() || ""; + } + + 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 ""; + } finally { + countryPromise = null; + } + })(); + + return countryPromise; }; From aba966d8bc2cc828b89e016a00373a09da880757 Mon Sep 17 00:00:00 2001 From: shayan khaleghparast Date: Fri, 15 Nov 2024 18:34:56 +0800 Subject: [PATCH 2/8] chore: resolved pr comments --- src/utils/__test__/country.utils.spec.ts | 58 ++++++++++++++---------- src/utils/country.utils.ts | 8 ++-- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/utils/__test__/country.utils.spec.ts b/src/utils/__test__/country.utils.spec.ts index bd5d4e7..5027ccc 100644 --- a/src/utils/__test__/country.utils.spec.ts +++ b/src/utils/__test__/country.utils.spec.ts @@ -1,38 +1,46 @@ -import { describe, beforeEach, afterEach, it, expect, vi } from "vitest"; -import { getCountry } from "../country.utils"; +import { describe, beforeEach, it, expect, vi, Mock } from 'vitest'; +import { getCountry } from '../country.utils'; +import Cookies from 'js-cookie'; -describe("getCountry", () => { - beforeEach(() => { - global.fetch = vi.fn(); - }); - afterEach(() => { - vi.resetAllMocks(); +vi.mock('js-cookie'); +global.fetch = vi.fn(); + +describe('getCountry', () => { + beforeEach(() => { + 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')); + // Mock cookie value as a JSON string + (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(''); }); }); diff --git a/src/utils/country.utils.ts b/src/utils/country.utils.ts index 3d68263..14179ad 100644 --- a/src/utils/country.utils.ts +++ b/src/utils/country.utils.ts @@ -28,17 +28,15 @@ export const getCountry = async (): Promise => { try { const response = await fetch(cloudflareTrace).catch(() => null); if (!response) { - return JSON.parse(Cookies.get("website_status") || "{}")?.loc?.toLowerCase() || ""; + return JSON.parse(Cookies.get("website_status") || "{}")?.clients_country || ""; } 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 || ""; + return data.loc?.toLowerCase() || JSON.parse(Cookies.get("website_status") || "{}")?.clients_country || ""; } catch (error) { - return ""; - } finally { - countryPromise = null; + return JSON.parse(Cookies.get("website_status") || "{}")?.clients_country?.toLowerCase() || ""; } })(); From 0119cff4aa0684b4ce4941bbe8260480c9784fe6 Mon Sep 17 00:00:00 2001 From: shayan khaleghparast Date: Fri, 15 Nov 2024 18:35:55 +0800 Subject: [PATCH 3/8] fix: updated test files --- src/utils/__test__/country.utils.spec.ts | 33 ++++++++++++------------ 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/utils/__test__/country.utils.spec.ts b/src/utils/__test__/country.utils.spec.ts index 5027ccc..0ce2b01 100644 --- a/src/utils/__test__/country.utils.spec.ts +++ b/src/utils/__test__/country.utils.spec.ts @@ -1,46 +1,45 @@ -import { describe, beforeEach, it, expect, vi, Mock } from 'vitest'; -import { getCountry } from '../country.utils'; -import Cookies from 'js-cookie'; +import { describe, beforeEach, it, expect, vi, Mock } from "vitest"; +import { getCountry } from "../country.utils"; +import Cookies from "js-cookie"; - -vi.mock('js-cookie'); +vi.mock("js-cookie"); global.fetch = vi.fn(); -describe('getCountry', () => { +describe("getCountry", () => { beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); }); - it('should return country code from Cloudflare trace', async () => { + it("should return country code from Cloudflare trace", async () => { (global.fetch as Mock).mockResolvedValueOnce({ - text: () => Promise.resolve('loc=US\nother=value'), + text: () => Promise.resolve("loc=US\nother=value"), }); const result = await getCountry(); - expect(result).toBe('us'); + expect(result).toBe("us"); }); - it('should return country code from cookie when Cloudflare fails', async () => { + it("should return country code from cookie when Cloudflare fails", async () => { vi.clearAllMocks(); vi.resetModules(); - (global.fetch as Mock).mockRejectedValueOnce(new Error('Network error')); + (global.fetch as Mock).mockRejectedValueOnce(new Error("Network error")); // Mock cookie value as a JSON string (Cookies.get as Mock).mockReturnValue(JSON.stringify({ clients_country: "fr" })); - const { getCountry } = await import('../country.utils'); + const { getCountry } = await import("../country.utils"); const result = await getCountry(); - expect(result).toBe('fr'); + expect(result).toBe("fr"); }); - it('should return empty string if no country data is available', async () => { + 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')); + (global.fetch as Mock).mockRejectedValueOnce(new Error("Network error")); (Cookies.get as Mock).mockReturnValue(JSON.stringify({})); - const { getCountry } = await import('../country.utils'); + const { getCountry } = await import("../country.utils"); const result = await getCountry(); - expect(result).toBe(''); + expect(result).toBe(""); }); }); From db6a89fc3022356284b318b58abff2b2399a8a0d Mon Sep 17 00:00:00 2001 From: shayan khaleghparast Date: Fri, 15 Nov 2024 18:36:59 +0800 Subject: [PATCH 4/8] chore: removed comments --- src/utils/__test__/country.utils.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/__test__/country.utils.spec.ts b/src/utils/__test__/country.utils.spec.ts index 0ce2b01..a4fef83 100644 --- a/src/utils/__test__/country.utils.spec.ts +++ b/src/utils/__test__/country.utils.spec.ts @@ -25,7 +25,6 @@ describe("getCountry", () => { vi.resetModules(); (global.fetch as Mock).mockRejectedValueOnce(new Error("Network error")); - // Mock cookie value as a JSON string (Cookies.get as Mock).mockReturnValue(JSON.stringify({ clients_country: "fr" })); const { getCountry } = await import("../country.utils"); From 73bc716cf9af7271a048be3b0fe03a0063bfc7be Mon Sep 17 00:00:00 2001 From: Shayan Khaleghparast <100833613+shayan-deriv@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:36:30 +0800 Subject: [PATCH 5/8] Update src/utils/country.utils.ts Co-authored-by: amam-deriv --- src/utils/country.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/country.utils.ts b/src/utils/country.utils.ts index 14179ad..717039a 100644 --- a/src/utils/country.utils.ts +++ b/src/utils/country.utils.ts @@ -36,7 +36,7 @@ export const getCountry = async (): Promise => { const data: TraceData = entries.length ? Object.fromEntries(entries) : {}; return data.loc?.toLowerCase() || JSON.parse(Cookies.get("website_status") || "{}")?.clients_country || ""; } catch (error) { - return JSON.parse(Cookies.get("website_status") || "{}")?.clients_country?.toLowerCase() || ""; + return JSON.parse(Cookies.get("website_status") || "{}")?.clients_country?.toLowerCase() || "error"; } })(); From e6e3921c60d433cf9053a205b9d5c3dd1f8f8588 Mon Sep 17 00:00:00 2001 From: shayan khaleghparast Date: Mon, 18 Nov 2024 11:58:07 +0800 Subject: [PATCH 6/8] chore: cleaned up the code --- src/utils/country.utils.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/utils/country.utils.ts b/src/utils/country.utils.ts index 717039a..ec2f7bb 100644 --- a/src/utils/country.utils.ts +++ b/src/utils/country.utils.ts @@ -20,23 +20,22 @@ let countryPromise: Promise | null = null; */ export const getCountry = async (): Promise => { - if (countryPromise) { - return countryPromise; - } + 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 JSON.parse(Cookies.get("website_status") || "{}")?.clients_country || ""; - } + if (!response) return cookieCountry || "nodata"; 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") || "{}")?.clients_country || ""; - } catch (error) { - return JSON.parse(Cookies.get("website_status") || "{}")?.clients_country?.toLowerCase() || "error"; + if (!text) return cookieCountry || "nodata"; + + const data: TraceData = Object.fromEntries(text.split("\n").map((v) => v.split("=", 2))); + return data.loc?.toLowerCase() || cookieCountry || "nodata"; + } catch { + return cookieCountry || "error"; } })(); From a1a1081b4251550262579826fbd44bc7251521f5 Mon Sep 17 00:00:00 2001 From: shayan khaleghparast Date: Mon, 18 Nov 2024 12:11:46 +0800 Subject: [PATCH 7/8] chore: set fallback values as empty string in all cases --- src/utils/__test__/country.utils.spec.ts | 2 +- src/utils/country.utils.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/__test__/country.utils.spec.ts b/src/utils/__test__/country.utils.spec.ts index a4fef83..99389ef 100644 --- a/src/utils/__test__/country.utils.spec.ts +++ b/src/utils/__test__/country.utils.spec.ts @@ -39,6 +39,6 @@ describe("getCountry", () => { (Cookies.get as Mock).mockReturnValue(JSON.stringify({})); const { getCountry } = await import("../country.utils"); const result = await getCountry(); - expect(result).toBe(""); + expect(result).toBe("nodata"); }); }); diff --git a/src/utils/country.utils.ts b/src/utils/country.utils.ts index ec2f7bb..ddc1e9a 100644 --- a/src/utils/country.utils.ts +++ b/src/utils/country.utils.ts @@ -27,15 +27,15 @@ export const getCountry = async (): Promise => { countryPromise = (async () => { try { const response = await fetch(cloudflareTrace).catch(() => null); - if (!response) return cookieCountry || "nodata"; + if (!response) return cookieCountry || ""; const text = await response.text().catch(() => ""); - if (!text) return cookieCountry || "nodata"; + if (!text) return cookieCountry || ""; const data: TraceData = Object.fromEntries(text.split("\n").map((v) => v.split("=", 2))); - return data.loc?.toLowerCase() || cookieCountry || "nodata"; + return data.loc?.toLowerCase() || cookieCountry || ""; } catch { - return cookieCountry || "error"; + return cookieCountry || ""; } })(); From a8de1d25d19bd9c04ae062abfc83ef5e42c805de Mon Sep 17 00:00:00 2001 From: shayan khaleghparast Date: Mon, 18 Nov 2024 12:12:21 +0800 Subject: [PATCH 8/8] test: updated test case --- src/utils/__test__/country.utils.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/__test__/country.utils.spec.ts b/src/utils/__test__/country.utils.spec.ts index 99389ef..a4fef83 100644 --- a/src/utils/__test__/country.utils.spec.ts +++ b/src/utils/__test__/country.utils.spec.ts @@ -39,6 +39,6 @@ describe("getCountry", () => { (Cookies.get as Mock).mockReturnValue(JSON.stringify({})); const { getCountry } = await import("../country.utils"); const result = await getCountry(); - expect(result).toBe("nodata"); + expect(result).toBe(""); }); });