fix: harden identity, live access, drafts and setup (review only)
Source-reviewed hardening changes and regression tests. UNBUILT / UNTESTED: not a deployable release. Go formatting, compilation, automated regression tests, full frontend validation and staging acceptance remain required. See docs/hardening/VALIDATION.md. Main remains the original clean snapshot; no upstream Git history imported.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// Modified in the independent hardening fork; see docs/hardening/README.md.
|
||||
"use client"
|
||||
|
||||
import {
|
||||
@@ -113,12 +114,14 @@ function SupportChatPanelContent() {
|
||||
useSupportChatSystemTheme()
|
||||
|
||||
const messageListRef = useRef<SupportChatMessageListHandle | null>(null)
|
||||
const mountedRef = useRef(false)
|
||||
const [isEmbedded, setIsEmbedded] = useState(false)
|
||||
const [isCloseDialogOpen, setIsCloseDialogOpen] = useState(false)
|
||||
const [isClosingConversation, setIsClosingConversation] = useState(false)
|
||||
const [isNewRequestDialogOpen, setIsNewRequestDialogOpen] = useState(false)
|
||||
|
||||
const {
|
||||
identityScope,
|
||||
title,
|
||||
subtitle,
|
||||
themeColor,
|
||||
@@ -129,6 +132,8 @@ function SupportChatPanelContent() {
|
||||
loadOlderMessages,
|
||||
status,
|
||||
error,
|
||||
sending,
|
||||
uploadingAsset,
|
||||
isOpen,
|
||||
isVisible,
|
||||
setIsOpen,
|
||||
@@ -144,6 +149,7 @@ function SupportChatPanelContent() {
|
||||
setConversationSatisfaction,
|
||||
} = useSupportChatStore(
|
||||
useShallow((state) => ({
|
||||
identityScope: state.identityScope,
|
||||
title: state.title,
|
||||
subtitle: state.subtitle,
|
||||
themeColor: state.themeColor,
|
||||
@@ -154,6 +160,8 @@ function SupportChatPanelContent() {
|
||||
loadOlderMessages: state.loadOlderMessages,
|
||||
status: state.status,
|
||||
error: state.error,
|
||||
sending: state.sending,
|
||||
uploadingAsset: state.uploadingAsset,
|
||||
isOpen: state.isOpen,
|
||||
isVisible: state.isVisible,
|
||||
setIsOpen: state.setIsOpen,
|
||||
@@ -182,6 +190,7 @@ function SupportChatPanelContent() {
|
||||
if (document.visibilityState !== "visible") {
|
||||
return
|
||||
}
|
||||
if (useSupportChatStore.getState().conversation?.id !== conversation.id) return
|
||||
void markConversationRead().catch((readError) => {
|
||||
console.error("Failed to mark support chat conversation read", readError)
|
||||
})
|
||||
@@ -190,9 +199,9 @@ function SupportChatPanelContent() {
|
||||
useEffect(() => {
|
||||
return bindSupportHostBridge({
|
||||
onInit: () => {
|
||||
if (useSupportChatStore.getState().isOpen) {
|
||||
bootstrap()
|
||||
}
|
||||
// Clear a previous visitor's private state even while the host keeps
|
||||
// this frame hidden; visibility must never race identity replacement.
|
||||
void bootstrap()
|
||||
},
|
||||
onOpen: () => {
|
||||
setIsOpen(true)
|
||||
@@ -205,14 +214,16 @@ function SupportChatPanelContent() {
|
||||
}, [bootstrap, setIsOpen, setIsVisible])
|
||||
|
||||
useEffect(() => {
|
||||
bootstrap()
|
||||
void bootstrap()
|
||||
}, [isOpen, bootstrap])
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true
|
||||
return () => {
|
||||
if (!isOpen) {
|
||||
disconnectSocket()
|
||||
}
|
||||
mountedRef.current = false
|
||||
disconnectSocket()
|
||||
}
|
||||
}, [isOpen, bootstrap, disconnectSocket])
|
||||
}, [disconnectSocket])
|
||||
|
||||
useEffect(() => {
|
||||
maybeMarkConversationRead()
|
||||
@@ -236,9 +247,21 @@ function SupportChatPanelContent() {
|
||||
}
|
||||
}, [maybeMarkConversationRead])
|
||||
|
||||
function requireDisplayedConversation() {
|
||||
const current = useSupportChatStore.getState()
|
||||
if (!conversation?.id || current.identityScope !== identityScope || current.conversation?.id !== conversation.id) {
|
||||
throw new Error(t("supportChat.sendUnavailable"))
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSend(content: string) {
|
||||
requireDisplayedConversation()
|
||||
const submittedConversationId = conversation?.id
|
||||
await handleSendMessage(content)
|
||||
messageListRef.current?.scrollToBottom()
|
||||
const current = useSupportChatStore.getState()
|
||||
if (current.identityScope === identityScope && current.conversation?.id === submittedConversationId) {
|
||||
messageListRef.current?.scrollToBottom()
|
||||
}
|
||||
}
|
||||
|
||||
function handleCloseWindow() {
|
||||
@@ -246,7 +269,8 @@ function SupportChatPanelContent() {
|
||||
if (isEmbedded) {
|
||||
requestSupportHostMinimize()
|
||||
} else {
|
||||
window.location.replace(getStandaloneClosedUrl())
|
||||
setIsOpen(false)
|
||||
window.location.replace(getStandaloneClosedUrl(window.location.search))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +281,7 @@ function SupportChatPanelContent() {
|
||||
setIsClosingConversation(true)
|
||||
try {
|
||||
if (conversation?.id) {
|
||||
requireDisplayedConversation()
|
||||
await closeConversation()
|
||||
}
|
||||
setIsCloseDialogOpen(false)
|
||||
@@ -351,15 +376,29 @@ function SupportChatPanelContent() {
|
||||
conversationId={conversation.id}
|
||||
customerToken={getCustomerSessionToken()}
|
||||
initialSatisfaction={conversation.satisfaction}
|
||||
onSubmitted={setConversationSatisfaction}
|
||||
onSubmitted={(satisfaction) => {
|
||||
if (mountedRef.current && useSupportChatStore.getState().identityScope === identityScope) {
|
||||
setConversationSatisfaction(satisfaction)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<CustomerMessageEditor
|
||||
conversationId={conversation?.id}
|
||||
draftScope={identityScope}
|
||||
sending={sending}
|
||||
uploadingAsset={uploadingAsset}
|
||||
disabled={!conversation || conversation.status === 4}
|
||||
onSend={handleSend}
|
||||
onUploadImage={uploadMessageImage}
|
||||
onSendAttachment={sendAttachment}
|
||||
onUploadImage={async (file) => {
|
||||
requireDisplayedConversation()
|
||||
return uploadMessageImage(file)
|
||||
}}
|
||||
onSendAttachment={async (file) => {
|
||||
requireDisplayedConversation()
|
||||
await sendAttachment(file)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user