Skip to content

Commit c5bddde

Browse files
Ahtisham992Copilot
andauthored
fix(client): handle joining a deleted channel gracefully (#471)
* fix(client): handle joining a deleted channel gracefully * fix: address copilot review comments * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent c9ed1bc commit c5bddde

6 files changed

Lines changed: 90 additions & 8 deletions

File tree

backend/api/chatHash/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,16 @@ router.get(
5050
"/status/:channel",
5151
asyncHandler(async (req, res) => {
5252
const { channel } = req.params;
53-
const { valid } = await channelValid(channel);
53+
const { valid, state } = await channelValid(channel);
5454

5555
if (!valid) {
56-
return res.sendStatus(404).send("Invalid channel");
56+
if (state === CHANNEL_STATE.DELETED) {
57+
return res.status(410).send({ error: "Channel deleted", state });
58+
}
59+
return res.status(404).send({ error: "Invalid channel", state });
5760
}
5861

59-
return res.send({ status: "ok" });
62+
return res.send({ status: "ok", state });
6063
})
6164
);
6265
router.delete(

client/src/components/ChatContainer/ChatHeader.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
import React, { useState } from 'react';
66
import { useChat } from '../../context/ChatContext';
77
import { Button } from '../common/Button';
8-
import { CopyIcon, ShareIcon, PhoneIcon } from '../common/icons';
8+
import { CopyIcon, ShareIcon, PhoneIcon, TrashIcon } from '../common/icons';
99
import './ChatHeader.css';
1010

1111
interface ChatHeaderProps {
1212
onStartCall: () => void;
1313
}
1414

1515
export const ChatHeader: React.FC<ChatHeaderProps> = ({ onStartCall }) => {
16-
const { isConnected, channelHash } = useChat();
16+
const { isConnected, channelHash, deleteChannel } = useChat();
1717
const [hashCopied, setHashCopied] = useState(false);
1818

1919
const handleCopyHash = () => {
@@ -36,6 +36,18 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({ onStartCall }) => {
3636
}
3737
};
3838

39+
const handleDelete = async () => {
40+
if (!window.confirm('Are you sure you want to delete this secure channel? This cannot be undone.')) return;
41+
42+
try {
43+
await deleteChannel();
44+
window.location.hash = ''; // Clear URL hash
45+
} catch (err) {
46+
console.error('Failed to delete channel:', err);
47+
alert((err as any).message || 'Failed to delete channel');
48+
}
49+
};
50+
3951
return (
4052
<header className={`chat-header glass ${isConnected ? 'active' : ''}`}>
4153
<div className="header-info">
@@ -81,6 +93,15 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({ onStartCall }) => {
8193
>
8294
<PhoneIcon size={20} />
8395
</Button>
96+
<Button
97+
className="btn--icon"
98+
variant="danger"
99+
onClick={handleDelete}
100+
title="Delete Chat"
101+
disabled={!channelHash}
102+
>
103+
<TrashIcon size={20} />
104+
</Button>
84105
</div>
85106
</header>
86107
);

client/src/components/SetupOverlay/SetupOverlay.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface SetupOverlayProps {
1414
isHidden: boolean;
1515
}
1616

17-
type ViewType = 'initial' | 'create' | 'join';
17+
type ViewType = 'initial' | 'create' | 'join' | 'deleted';
1818

1919
export const SetupOverlay: React.FC<SetupOverlayProps> = ({ onSetupComplete, isHidden }) => {
2020
const { createNewChannel } = useChat();
@@ -88,8 +88,13 @@ export const SetupOverlay: React.FC<SetupOverlayProps> = ({ onSetupComplete, isH
8888
setIsLoading(true);
8989
setStatus('Connecting...');
9090
await onSetupComplete(joinHash);
91-
} catch (err) {
92-
setStatus('Failed to join channel. Please check the hash and try again.');
91+
} catch (err: any) {
92+
if (err.message === 'CHANNEL_DELETED') {
93+
setView('deleted');
94+
setStatus('');
95+
} else {
96+
setStatus('Failed to join channel. Please check the hash and try again.');
97+
}
9398
console.error('Join error:', err);
9499
} finally {
95100
setIsLoading(false);
@@ -127,6 +132,16 @@ export const SetupOverlay: React.FC<SetupOverlayProps> = ({ onSetupComplete, isH
127132
/>
128133
)}
129134

135+
{view === 'deleted' && (
136+
<div style={{ textAlign: 'center', margin: '2rem 0' }}>
137+
<h2 style={{ color: '#ef4444', marginBottom: '1rem' }}>Channel Deleted</h2>
138+
<p style={{ opacity: 0.8, marginBottom: '2rem' }}>This secure channel has been permanently deleted and can no longer be accessed.</p>
139+
<button className="btn btn--primary" onClick={handleBack}>
140+
Return Home
141+
</button>
142+
</div>
143+
)}
144+
130145
{status && <div className="setup-status">{status}</div>}
131146
</div>
132147
</div>

client/src/components/common/icons.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,20 @@ export const EndCallIcon: React.FC<IconProps> = ({ size = 24, className = '' })
9494
<path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
9595
</svg>
9696
);
97+
98+
export const TrashIcon: React.FC<IconProps> = ({ size = 24, className = '' }) => (
99+
<svg
100+
viewBox="0 0 24 24"
101+
fill="none"
102+
stroke="currentColor"
103+
strokeWidth="2"
104+
strokeLinecap="round"
105+
strokeLinejoin="round"
106+
width={size}
107+
height={size}
108+
className={className}
109+
>
110+
<polyline points="3 6 5 6 21 6"></polyline>
111+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
112+
</svg>
113+
);

client/src/context/ChatContext.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ export const ChatProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
5757
async (hash: string) => {
5858
if (!chat) throw new Error('Chat not initialized');
5959
try {
60+
// Check for channel status before joining
61+
const baseUrl = process.env.CHATE2EE_API_URL || 'http://localhost:3001';
62+
const statusRes = await fetch(`${baseUrl}/api/chat-link/status/${encodeURIComponent(hash)}`);
63+
if (statusRes.status === 410) {
64+
throw new Error('CHANNEL_DELETED');
65+
}
66+
if (!statusRes.ok) {
67+
throw new Error('Failed to verify channel status');
68+
}
69+
6070
// Auto-generate User ID
6171
const newUserId = (utils as any).generateUUID();
6272
setUserId(newUserId);
@@ -181,6 +191,20 @@ export const ChatProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
181191
}
182192
};
183193

194+
// Delete channel
195+
const deleteChannel = useCallback(async () => {
196+
if (!chat) return;
197+
try {
198+
await chat.delete();
199+
setIsConnected(false);
200+
setChannelHash('');
201+
setMessages([]);
202+
} catch (err) {
203+
console.error('Failed to delete channel:', err);
204+
throw err;
205+
}
206+
}, [chat]);
207+
184208
const value: ChatContextType = {
185209
chat,
186210
userId,
@@ -199,6 +223,7 @@ export const ChatProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
199223
endCall,
200224
addMessage,
201225
setCallDuration,
226+
deleteChannel,
202227
};
203228

204229
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;

client/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface ChatContextType {
4949
endCall: () => Promise<void>;
5050
addMessage: (message: Message) => void;
5151
setCallDuration: (duration: number) => void;
52+
deleteChannel: () => Promise<void>;
5253
}
5354

5455
// Common component props

0 commit comments

Comments
 (0)