142 lines
4 KiB
TypeScript
142 lines
4 KiB
TypeScript
|
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||
|
|
import { Linking } from "react-native";
|
||
|
|
import {
|
||
|
|
NavigationContainer,
|
||
|
|
type LinkingOptions,
|
||
|
|
useNavigationContainerRef,
|
||
|
|
} from "@react-navigation/native";
|
||
|
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||
|
|
import { StatusBar } from "expo-status-bar";
|
||
|
|
import AppNavigator from "./navigation/AppNavigator";
|
||
|
|
import type { RootStackParamList } from "./navigation/types";
|
||
|
|
import { SessionProvider, useSession } from "./state/session-context";
|
||
|
|
import { getNavigationTheme, useTheme } from "./theme";
|
||
|
|
|
||
|
|
function extractGameId(url: string): string | null {
|
||
|
|
try {
|
||
|
|
const parsed = new URL(url);
|
||
|
|
const path = parsed.pathname || "";
|
||
|
|
if (parsed.protocol === "https:" || parsed.protocol === "http:") {
|
||
|
|
const match = path.match(/^\/play\/?([^/]+)?/);
|
||
|
|
const id = match?.[1]?.trim();
|
||
|
|
return id ? id : null;
|
||
|
|
}
|
||
|
|
if (parsed.host === "play") {
|
||
|
|
const id = path.replace(/^\//, "").trim();
|
||
|
|
return id ? id : null;
|
||
|
|
}
|
||
|
|
const fallbackMatch = path.match(/^\/play\/?([^/]+)?/);
|
||
|
|
const fallbackId = fallbackMatch?.[1]?.trim();
|
||
|
|
return fallbackId ? fallbackId : null;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function logDeepLink(url: string) {
|
||
|
|
if (!__DEV__) return;
|
||
|
|
const gameId = extractGameId(url);
|
||
|
|
console.log(`[deep-link] url=${url} gameId=${gameId ?? "invalid"}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
function RootNavigationGate() {
|
||
|
|
const manager = useSession();
|
||
|
|
const navigationRef = useNavigationContainerRef<RootStackParamList>();
|
||
|
|
const [navReady, setNavReady] = useState(false);
|
||
|
|
const lastTargetRef = useRef<keyof RootStackParamList | null>(null);
|
||
|
|
const lastLinkRef = useRef<string | null>(null);
|
||
|
|
const theme = useTheme();
|
||
|
|
const navigationTheme = getNavigationTheme(theme);
|
||
|
|
const linking = useMemo<LinkingOptions<RootStackParamList>>(
|
||
|
|
() => ({
|
||
|
|
prefixes: ["negopoly://", "https://negopoly.fr"],
|
||
|
|
config: {
|
||
|
|
screens: {
|
||
|
|
Entry: "play/:gameId",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
getInitialURL: async () => {
|
||
|
|
const url = await Linking.getInitialURL();
|
||
|
|
if (url) {
|
||
|
|
lastLinkRef.current = url;
|
||
|
|
logDeepLink(url);
|
||
|
|
}
|
||
|
|
return url;
|
||
|
|
},
|
||
|
|
subscribe: (listener) => {
|
||
|
|
const onReceiveURL = ({ url }: { url: string }) => {
|
||
|
|
if (!url) return;
|
||
|
|
if (lastLinkRef.current === url) return;
|
||
|
|
lastLinkRef.current = url;
|
||
|
|
logDeepLink(url);
|
||
|
|
listener(url);
|
||
|
|
};
|
||
|
|
const subscription = Linking.addEventListener("url", onReceiveURL);
|
||
|
|
return () => subscription.remove();
|
||
|
|
},
|
||
|
|
}),
|
||
|
|
[],
|
||
|
|
);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!navReady || !navigationRef.isReady()) return;
|
||
|
|
|
||
|
|
let target: keyof RootStackParamList;
|
||
|
|
if (!manager.sessionId) {
|
||
|
|
target = "Entry";
|
||
|
|
} else if (!manager.session) {
|
||
|
|
target = manager.connectionState === "error" ? "Entry" : "Lobby";
|
||
|
|
} else if (manager.session.status === "lobby") {
|
||
|
|
target = "Lobby";
|
||
|
|
} else if (manager.isBanker) {
|
||
|
|
target = "BankerTabs";
|
||
|
|
} else {
|
||
|
|
target = "PlayerTabs";
|
||
|
|
}
|
||
|
|
|
||
|
|
const currentRoute = navigationRef.getCurrentRoute();
|
||
|
|
if (currentRoute?.name === target || lastTargetRef.current === target) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
navigationRef.reset({
|
||
|
|
index: 0,
|
||
|
|
routes: [{ name: target }],
|
||
|
|
});
|
||
|
|
lastTargetRef.current = target;
|
||
|
|
}, [
|
||
|
|
manager.sessionId,
|
||
|
|
manager.session,
|
||
|
|
manager.isBanker,
|
||
|
|
manager.connectionState,
|
||
|
|
navReady,
|
||
|
|
navigationRef,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<NavigationContainer
|
||
|
|
ref={navigationRef}
|
||
|
|
onReady={() => setNavReady(true)}
|
||
|
|
linking={linking}
|
||
|
|
theme={navigationTheme}
|
||
|
|
>
|
||
|
|
<AppNavigator />
|
||
|
|
</NavigationContainer>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function App() {
|
||
|
|
const theme = useTheme();
|
||
|
|
return (
|
||
|
|
<SafeAreaProvider>
|
||
|
|
<StatusBar
|
||
|
|
style={theme.dark ? "light" : "dark"}
|
||
|
|
backgroundColor={theme.colors.background}
|
||
|
|
/>
|
||
|
|
<SessionProvider>
|
||
|
|
<RootNavigationGate />
|
||
|
|
</SessionProvider>
|
||
|
|
</SafeAreaProvider>
|
||
|
|
);
|
||
|
|
}
|