From 94e4e9300b6afc000cee371cc116f7431ce42e22 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Fri, 18 Apr 2025 17:01:29 -0600 Subject: [PATCH] send region to backend --- src/backend/accounts/region.ts | 44 +++++++++++ src/pages/parts/admin/RegionSelectorPart.tsx | 19 ++++- src/pages/parts/settings/SetupPart.tsx | 14 ++-- src/utils/detectRegion.tsx | 83 ++++++++++++++++---- 4 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 src/backend/accounts/region.ts diff --git a/src/backend/accounts/region.ts b/src/backend/accounts/region.ts new file mode 100644 index 00000000..d4246605 --- /dev/null +++ b/src/backend/accounts/region.ts @@ -0,0 +1,44 @@ +import { ofetch } from "ofetch"; + +import { getAuthHeaders } from "@/backend/accounts/auth"; +import { AccountWithToken } from "@/stores/auth"; + +export type Region = + | "us-east" + | "us-west" + | "south" + | "asia" + | "europe" + | "unknown"; + +export interface RegionResponse { + region: Region; + lastChecked: number; + userPicked: boolean; +} + +export async function updateRegion( + url: string, + account: AccountWithToken, + region: Region, + userPicked: boolean = false, +) { + return ofetch(`/users/${account.userId}/region`, { + method: "PUT", + headers: getAuthHeaders(account.token), + baseURL: url, + body: { + region, + userPicked, + lastChecked: Math.floor(Date.now() / 1000), + }, + }); +} + +export async function getRegion(url: string, account: AccountWithToken) { + return ofetch(`/users/${account.userId}/region`, { + method: "GET", + headers: getAuthHeaders(account.token), + baseURL: url, + }); +} diff --git a/src/pages/parts/admin/RegionSelectorPart.tsx b/src/pages/parts/admin/RegionSelectorPart.tsx index b93be846..c0b5f549 100644 --- a/src/pages/parts/admin/RegionSelectorPart.tsx +++ b/src/pages/parts/admin/RegionSelectorPart.tsx @@ -1,10 +1,14 @@ +import { useState } from "react"; + +import { Region } from "@/backend/accounts/region"; import { Dropdown } from "@/components/form/Dropdown"; import { Box } from "@/components/layout/Box"; import { Heading2 } from "@/components/utils/Text"; -import { Region, useRegionStore } from "@/utils/detectRegion"; +import { useRegionStore } from "@/utils/detectRegion"; export function RegionSelectorPart() { const { region, setRegion } = useRegionStore(); + const [isUpdating, setIsUpdating] = useState(false); const regionOptions = [ { id: "us-east", name: "US East (Ohio)" }, @@ -14,6 +18,17 @@ export function RegionSelectorPart() { { id: "europe", name: "Europe Central (London)" }, ]; + const handleRegionChange = async (item: { id: string; name: string }) => { + setIsUpdating(true); + try { + await setRegion(item.id as Region, true); + } catch (error) { + console.error("Failed to update region:", error); + } finally { + setIsUpdating(false); + } + }; + return ( <> Region Selector @@ -33,7 +48,7 @@ export function RegionSelectorPart() { regionOptions.find((r) => r.id === region)?.name || "Unknown (US East)", }} - setSelectedItem={(item) => setRegion(item.id as Region, true)} + setSelectedItem={handleRegionChange} direction="up" /> diff --git a/src/pages/parts/settings/SetupPart.tsx b/src/pages/parts/settings/SetupPart.tsx index 59c4dcc7..3d8fd388 100644 --- a/src/pages/parts/settings/SetupPart.tsx +++ b/src/pages/parts/settings/SetupPart.tsx @@ -19,7 +19,7 @@ import { Heading3 } from "@/components/utils/Text"; import { conf } from "@/setup/config"; import { useAuthStore } from "@/stores/auth"; -const getRegion = (): string | null => { +const getRegion = async (): Promise => { if (typeof window === "undefined") return null; try { const regionData = window.localStorage.getItem("__MW::region"); @@ -31,25 +31,27 @@ const getRegion = (): string | null => { } }; -const getBaseUrl = (): string => { - const region = getRegion(); +const getBaseUrl = async (): Promise => { + const region = await getRegion(); switch (region) { case "us-east": return "https://fed-api-east.pstream.org"; case "us-west": return "https://fed-api-west.pstream.org"; - case "south-america": + case "south": return "https://fed-api-south.pstream.org"; case "asia": return "https://fed-api-asia.pstream.org"; case "europe": return "https://fed-api-europe.pstream.org"; - default: + case "unknown": return "https://fed-api-east.pstream.org"; + default: + return ""; } }; -const BASE_URL = getBaseUrl(); +const BASE_URL = await getBaseUrl(); const testUrl = "https://postman-echo.com/get"; const febboxApiTestUrl = `${BASE_URL}/movie/tt13654226`; diff --git a/src/utils/detectRegion.tsx b/src/utils/detectRegion.tsx index 6522d5da..849ac50b 100644 --- a/src/utils/detectRegion.tsx +++ b/src/utils/detectRegion.tsx @@ -1,13 +1,9 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -export type Region = - | "us-east" - | "us-west" - | "south" - | "asia" - | "europe" - | "unknown"; +import { Region, getRegion, updateRegion } from "@/backend/accounts/region"; +import { conf } from "@/setup/config"; +import { useAuthStore } from "@/stores/auth"; interface RegionStore { region: Region | null; @@ -22,8 +18,34 @@ export const useRegionStore = create()( region: null, lastChecked: null, userPicked: false, - setRegion: (region, userPicked = false) => - set({ region, lastChecked: Math.floor(Date.now() / 1000), userPicked }), + setRegion: async (region, userPicked = false) => { + const url = conf().BACKEND_URL; + const account = useAuthStore.getState().account; + + if (url && account) { + try { + const response = await updateRegion( + url, + account, + region, + userPicked, + ); + set({ + region: response.region, + lastChecked: response.lastChecked, + userPicked: response.userPicked, + }); + } catch (error) { + console.error("Failed to update region:", error); + } + } else { + set({ + region, + lastChecked: Math.floor(Date.now() / 1000), + userPicked, + }); + } + }, }), { name: "__MW::region", @@ -82,24 +104,55 @@ function determineRegion(data: { return closestRegion; } +// 1. Check if user manually picked a region (highest priority) +// 2. Check if we need to refresh the region +// 3. If refresh needed: +// a. Try to get fresh region from backend +// b. If backend region is fresh, use it +// c. If backend region is expired or unavailable, fall back to IP detection +// 4. If no refresh needed, use existing region + export async function detectRegion(): Promise { const store = useRegionStore.getState(); + const url = conf().BACKEND_URL; + const account = useAuthStore.getState().account; // If user picked a region, always return that if (store.userPicked && store.region) { return store.region; } - // If we have a recent detection, return that - if ( - store.region && - store.lastChecked && - Math.floor(Date.now() / 1000) - store.lastChecked < 2592000 // 30 days in seconds - ) { + // Check if we need to refresh the region + const needsRefresh = + !store.region || + !store.lastChecked || + Math.floor(Date.now() / 1000) - store.lastChecked >= 2592000; // 30 days in seconds + + if (!needsRefresh && store.region) { return store.region; } try { + // Try to get fresh region from backend first + if (url && account) { + try { + const response = await getRegion(url, account); + // Only update if the backend has a fresh region + if ( + response.lastChecked && + Math.floor(Date.now() / 1000) - response.lastChecked < 2592000 + ) { + if (!store.userPicked) { + store.setRegion(response.region, response.userPicked); + } + return response.region; + } + } catch (error) { + console.warn("Failed to get region from backend:", error); + } + } + + // Fallback to IP-based detection const response = await fetch("https://ipapi.co/json/"); const data = await response.json();