feat(chat): prompt facebook users to open external browser

This commit is contained in:
2026-06-18 10:59:13 +08:00
parent d2bced8889
commit 172ef28060
9 changed files with 242 additions and 30 deletions
+64 -2
View File
@@ -17,16 +17,19 @@
* - `ChatLogout` → 登出(机器自动 cleanup WS actor
* - chat 机器内部用 fromCallback actor 完整管理 WS 生命周期
*/
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useAuthState } from "@/stores/auth/auth-context";
import type { LoginStatus } from "@/data/dto/auth";
import { AuthStorage } from "@/data/storage/auth/auth_storage";
import { UserStorage } from "@/data/storage/user/user_storage";
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
import { AppEnvUtil } from "@/utils/app-env";
import { ROUTES } from "@/router/routes";
import { BrowserDetector } from "@/utils/browser-detect";
import { UrlLauncherUtil } from "@/utils/url-launcher-util";
import { ROUTE_BUILDERS, ROUTES } from "@/router/routes";
import { MobileShell } from "@/app/_components/core";
@@ -36,6 +39,7 @@ import {
ChatHeader,
ChatInputBar,
ChatQuotaExhaustedBanner,
ExternalBrowserDialog,
PwaInstallOverlay,
} from "./components";
import styles from "./components/chat-screen.module.css";
@@ -81,6 +85,8 @@ export function ChatScreen() {
const state = useChatState();
const authState = useAuthState();
const chatDispatch = useChatDispatch();
const [showExternalBrowserDialog, setShowExternalBrowserDialog] =
useState(false);
// isGuest 派生(chat-screen 是唯一知道这个值的地方 —— chat 机器无 isGuest 字段)
const isGuest = deriveIsGuest(authState.loginStatus);
@@ -103,6 +109,7 @@ export function ChatScreen() {
// - 登录态变化(guest→email 等)→ 派 ChatGuestLogin 或 ChatNonGuestLogin
// ─────────────────────────────────────────────────────────────
const prevLoginStatusRef = useRef<LoginStatus | null>(null);
const externalBrowserPromptShownRef = useRef(false);
useEffect(() => {
if (!authState.hasInitialized || authState.isLoading) return;
@@ -125,6 +132,55 @@ export function ChatScreen() {
router,
]);
useEffect(() => {
if (!authState.hasInitialized || authState.isLoading) return;
if (authState.loginStatus !== "facebook") return;
if (externalBrowserPromptShownRef.current) return;
if (!BrowserDetector.isInAppBrowser()) return;
externalBrowserPromptShownRef.current = true;
const timer = window.setTimeout(() => {
setShowExternalBrowserDialog(true);
}, 3000);
return () => window.clearTimeout(timer);
}, [
authState.hasInitialized,
authState.isLoading,
authState.loginStatus,
]);
async function handleOpenExternalBrowser(): Promise<void> {
setShowExternalBrowserDialog(false);
const authStorage = AuthStorage.getInstance();
const userStorage = UserStorage.getInstance();
const [deviceIdR, facebookIdR, avatarUrlR] = await Promise.all([
authStorage.getDeviceId(),
authStorage.getFacebookId(),
userStorage.getAvatarUrl(),
]);
const deviceId = deviceIdR.success ? deviceIdR.data : null;
const facebookId = facebookIdR.success ? facebookIdR.data : null;
const avatarUrl = avatarUrlR.success ? avatarUrlR.data : null;
if (deviceId && facebookId) {
UrlLauncherUtil.openUrlWithExternalBrowser(
ROUTE_BUILDERS.chatDeviceId(deviceId),
{
queryParams: {
fbid: facebookId,
...(avatarUrl ? { avatarUrl } : {}),
},
},
);
return;
}
UrlLauncherUtil.openUrlWithExternalBrowser(ROUTES.chat);
}
return (
<MobileShell>
<div className={styles.shell}>
@@ -162,6 +218,12 @@ export function ChatScreen() {
{/* PWA 安装提示触发器(无可见 UI3.5s 后弹 dialog */}
<PwaInstallOverlay />
<ExternalBrowserDialog
open={showExternalBrowserDialog}
onClose={() => setShowExternalBrowserDialog(false)}
onConfirm={() => void handleOpenExternalBrowser()}
/>
</div>
</MobileShell>
);