refactor(app): tighten feature boundaries
This commit is contained in:
@@ -1,55 +0,0 @@
|
||||
"use client";
|
||||
|
||||
const STORAGE_KEY = "cozsweet.chat.pendingImageReturn";
|
||||
const MAX_AGE_MS = 30 * 60 * 1000;
|
||||
|
||||
export interface PendingChatImageReturn {
|
||||
reason: "image_paywall";
|
||||
messageId: string;
|
||||
returnUrl: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export function savePendingChatImageReturn(input: {
|
||||
messageId: string;
|
||||
returnUrl: string;
|
||||
}): void {
|
||||
if (typeof window === "undefined") return;
|
||||
const payload: PendingChatImageReturn = {
|
||||
reason: "image_paywall",
|
||||
messageId: input.messageId,
|
||||
returnUrl: input.returnUrl,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
export function consumePendingChatImageReturn(): PendingChatImageReturn | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
|
||||
const raw = window.sessionStorage.getItem(STORAGE_KEY);
|
||||
window.sessionStorage.removeItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
|
||||
try {
|
||||
const value = JSON.parse(raw) as Partial<PendingChatImageReturn>;
|
||||
if (value.reason !== "image_paywall") return null;
|
||||
if (typeof value.messageId !== "string" || value.messageId.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (typeof value.returnUrl !== "string" || value.returnUrl.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (typeof value.createdAt !== "number") return null;
|
||||
if (Date.now() - value.createdAt > MAX_AGE_MS) return null;
|
||||
|
||||
return {
|
||||
reason: value.reason,
|
||||
messageId: value.messageId,
|
||||
returnUrl: value.returnUrl,
|
||||
createdAt: value.createdAt,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,5 @@ export * from "./private-message-card";
|
||||
export * from "./pwa-install-dialog";
|
||||
export * from "./pwa-install-overlay";
|
||||
export * from "./text-bubble";
|
||||
export * from "./user-message-avatar";
|
||||
export * from "./voice-bubble";
|
||||
export * from "./voice-unlock-options-dialog";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
|
||||
import { UserMessageAvatar } from "./user-message-avatar";
|
||||
import { UserMessageAvatar } from "@/app/_components";
|
||||
import styles from "./message-avatar.module.css";
|
||||
|
||||
export interface MessageAvatarProps {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
import styles from "./message-avatar.module.css";
|
||||
|
||||
export interface UserMessageAvatarProps {
|
||||
avatarUrl?: string | null;
|
||||
className?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export function UserMessageAvatar({
|
||||
avatarUrl,
|
||||
className,
|
||||
size = 43,
|
||||
}: UserMessageAvatarProps) {
|
||||
const avatarClassName = [styles.avatar, className].filter(Boolean).join(" ");
|
||||
const avatarStyle = { width: size, height: size };
|
||||
|
||||
if (avatarUrl && avatarUrl.length > 0) {
|
||||
return (
|
||||
<div
|
||||
className={avatarClassName}
|
||||
style={avatarStyle}
|
||||
aria-label="User avatar"
|
||||
>
|
||||
<Image src={avatarUrl} alt="" width={size} height={size} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={avatarClassName}
|
||||
style={avatarStyle}
|
||||
aria-label="Guest avatar"
|
||||
>
|
||||
<Image
|
||||
src="/images/chat/pic-chat-guest.png"
|
||||
alt="Guest"
|
||||
width={size}
|
||||
height={size}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,16 +5,14 @@
|
||||
*
|
||||
* 设计:
|
||||
* - 不走 Server `redirect()`:避免把 fbid 暴露在 URL 里。
|
||||
* - deviceId / fbid 都走 `AuthStorage.getInstance().setXxx`(与 `token_interceptor.ts` 同源)
|
||||
* - avatarUrl 写入 UserStorage 的头像 slot,外部浏览器首次恢复时可带给后端。
|
||||
* - deviceId / fbid / avatarUrl 统一交给 `lib/chat` 落地。
|
||||
* - 写入完成后 `router.replace('/chat')`,URL 不留深链痕迹。
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import { UserStorage } from "@/data/storage/user/user_storage";
|
||||
import { persistExternalBrowserChatDeepLink } from "@/lib/chat/chat_external_browser";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { Logger } from "@/utils";
|
||||
|
||||
@@ -37,13 +35,11 @@ export default function DeepLinkPersist({
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
await AuthStorage.getInstance().setDeviceId(deviceId);
|
||||
if (fbid && fbid.length > 0) {
|
||||
await AuthStorage.getInstance().setFacebookId(fbid);
|
||||
}
|
||||
if (avatarUrl && avatarUrl.length > 0) {
|
||||
await UserStorage.getInstance().setAvatarUrl(avatarUrl);
|
||||
}
|
||||
await persistExternalBrowserChatDeepLink({
|
||||
deviceId,
|
||||
facebookId: fbid,
|
||||
avatarUrl,
|
||||
});
|
||||
} catch (err) {
|
||||
// 写不进 localStorage 时(例如隐私模式)也不阻塞跳转;记录到 console 即可。
|
||||
log.warn("[DeepLinkPersist] failed to persist", err);
|
||||
|
||||
@@ -6,9 +6,9 @@ import { MobileShell } from "@/app/_components/core";
|
||||
import { ROUTE_BUILDERS, ROUTES } from "@/router/routes";
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||
import { savePendingChatImageReturn } from "@/lib/navigation/chat_image_return_session";
|
||||
|
||||
import { getChatPaywallNavigationUrl } from "../../chat-screen.helpers";
|
||||
import { savePendingChatImageReturn } from "../../chat-image-return-session";
|
||||
import { FullscreenImageViewer, HistoryUnlockDialog } from "../../components";
|
||||
import styles from "./chat-image-viewer-screen.module.css";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user