mirror of
https://github.com/p-stream/p-stream.git
synced 2026-05-11 16:00:53 +00:00
send region to backend
This commit is contained in:
parent
6ed0192d9d
commit
94e4e9300b
4 changed files with 137 additions and 23 deletions
44
src/backend/accounts/region.ts
Normal file
44
src/backend/accounts/region.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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`;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue