send region to backend

This commit is contained in:
Pas 2025-04-18 17:01:29 -06:00
parent 6ed0192d9d
commit 94e4e9300b
4 changed files with 137 additions and 23 deletions

View file

@ -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<RegionResponse>(`/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<RegionResponse>(`/users/${account.userId}/region`, {
method: "GET",
headers: getAuthHeaders(account.token),
baseURL: url,
});
}

View file

@ -1,10 +1,14 @@
import { useState } from "react";
import { Region } from "@/backend/accounts/region";
import { Dropdown } from "@/components/form/Dropdown"; import { Dropdown } from "@/components/form/Dropdown";
import { Box } from "@/components/layout/Box"; import { Box } from "@/components/layout/Box";
import { Heading2 } from "@/components/utils/Text"; import { Heading2 } from "@/components/utils/Text";
import { Region, useRegionStore } from "@/utils/detectRegion"; import { useRegionStore } from "@/utils/detectRegion";
export function RegionSelectorPart() { export function RegionSelectorPart() {
const { region, setRegion } = useRegionStore(); const { region, setRegion } = useRegionStore();
const [isUpdating, setIsUpdating] = useState(false);
const regionOptions = [ const regionOptions = [
{ id: "us-east", name: "US East (Ohio)" }, { id: "us-east", name: "US East (Ohio)" },
@ -14,6 +18,17 @@ export function RegionSelectorPart() {
{ id: "europe", name: "Europe Central (London)" }, { 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 ( return (
<> <>
<Heading2 className="mb-8 mt-12">Region Selector</Heading2> <Heading2 className="mb-8 mt-12">Region Selector</Heading2>
@ -33,7 +48,7 @@ export function RegionSelectorPart() {
regionOptions.find((r) => r.id === region)?.name || regionOptions.find((r) => r.id === region)?.name ||
"Unknown (US East)", "Unknown (US East)",
}} }}
setSelectedItem={(item) => setRegion(item.id as Region, true)} setSelectedItem={handleRegionChange}
direction="up" direction="up"
/> />
</div> </div>

View file

@ -19,7 +19,7 @@ import { Heading3 } from "@/components/utils/Text";
import { conf } from "@/setup/config"; import { conf } from "@/setup/config";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
const getRegion = (): string | null => { const getRegion = async (): Promise<string | null> => {
if (typeof window === "undefined") return null; if (typeof window === "undefined") return null;
try { try {
const regionData = window.localStorage.getItem("__MW::region"); const regionData = window.localStorage.getItem("__MW::region");
@ -31,25 +31,27 @@ const getRegion = (): string | null => {
} }
}; };
const getBaseUrl = (): string => { const getBaseUrl = async (): Promise<string> => {
const region = getRegion(); const region = await getRegion();
switch (region) { switch (region) {
case "us-east": case "us-east":
return "https://fed-api-east.pstream.org"; return "https://fed-api-east.pstream.org";
case "us-west": case "us-west":
return "https://fed-api-west.pstream.org"; return "https://fed-api-west.pstream.org";
case "south-america": case "south":
return "https://fed-api-south.pstream.org"; return "https://fed-api-south.pstream.org";
case "asia": case "asia":
return "https://fed-api-asia.pstream.org"; return "https://fed-api-asia.pstream.org";
case "europe": case "europe":
return "https://fed-api-europe.pstream.org"; return "https://fed-api-europe.pstream.org";
default: case "unknown":
return "https://fed-api-east.pstream.org"; 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 testUrl = "https://postman-echo.com/get";
const febboxApiTestUrl = `${BASE_URL}/movie/tt13654226`; const febboxApiTestUrl = `${BASE_URL}/movie/tt13654226`;

View file

@ -1,13 +1,9 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
export type Region = import { Region, getRegion, updateRegion } from "@/backend/accounts/region";
| "us-east" import { conf } from "@/setup/config";
| "us-west" import { useAuthStore } from "@/stores/auth";
| "south"
| "asia"
| "europe"
| "unknown";
interface RegionStore { interface RegionStore {
region: Region | null; region: Region | null;
@ -22,8 +18,34 @@ export const useRegionStore = create<RegionStore>()(
region: null, region: null,
lastChecked: null, lastChecked: null,
userPicked: false, userPicked: false,
setRegion: (region, userPicked = false) => setRegion: async (region, userPicked = false) => {
set({ region, lastChecked: Math.floor(Date.now() / 1000), userPicked }), 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", name: "__MW::region",
@ -82,24 +104,55 @@ function determineRegion(data: {
return closestRegion; 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<Region> { export async function detectRegion(): Promise<Region> {
const store = useRegionStore.getState(); const store = useRegionStore.getState();
const url = conf().BACKEND_URL;
const account = useAuthStore.getState().account;
// If user picked a region, always return that // If user picked a region, always return that
if (store.userPicked && store.region) { if (store.userPicked && store.region) {
return store.region; return store.region;
} }
// If we have a recent detection, return that // Check if we need to refresh the region
if ( const needsRefresh =
store.region && !store.region ||
store.lastChecked && !store.lastChecked ||
Math.floor(Date.now() / 1000) - store.lastChecked < 2592000 // 30 days in seconds Math.floor(Date.now() / 1000) - store.lastChecked >= 2592000; // 30 days in seconds
) {
if (!needsRefresh && store.region) {
return store.region; return store.region;
} }
try { 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 response = await fetch("https://ipapi.co/json/");
const data = await response.json(); const data = await response.json();