This commit is contained in:
2025-10-19 20:31:29 +03:00
Unverified
parent 66f0b756a0
commit f5527ca14e
67 changed files with 891 additions and 902 deletions
@@ -1,7 +1,7 @@
import { MessagePanel } from "./MessagePanel";
import {
fetchDMHistory,
decryptDm,
import {
fetchDMHistory,
decryptDm,
sendDMViaWebSocket,
sendDmWithFiles,
editDmEnvelope,
@@ -43,7 +43,7 @@ export class DMPanel extends MessagePanel {
async activate(): Promise<void> {
// Don't load messages immediately during activation to prevent animation freeze
// Messages will be loaded after the animation completes
// Subscribe to recipient's online status
if (this.dmData?.userId) {
onlineStatusManager.subscribe(this.dmData.userId);
@@ -65,9 +65,9 @@ export class DMPanel extends MessagePanel {
private async parseTextPayload(env: DmEnvelope, decryptedMessages: Message[]) {
const plaintext = await decryptDm(env, this.dmData!.publicKey);
const username = formatDMUsername(
env.senderId,
env.recipientId,
this.currentUser.currentUser?.id!,
env.senderId,
env.recipientId,
this.currentUser.currentUser?.id!,
this.dmData!.username
);
@@ -146,10 +146,10 @@ export class DMPanel extends MessagePanel {
if (!this.currentUser.authToken || !this.dmData || !content.trim()) return;
try {
const payload: DmEncryptedJSON = {
type: "text",
data: {
content: content.trim(),
const payload: DmEncryptedJSON = {
type: "text",
data: {
content: content.trim(),
reply_to_id: replyToId ?? undefined
}
}
@@ -193,12 +193,12 @@ export class DMPanel extends MessagePanel {
async handleWebSocketMessage(response: DMWebSocketMessage): Promise<void> {
if (response.type === "dmNew" && this.dmData) {
const envelope = response.data;
// If this is for the active DM conversation
if (envelope.senderId === this.dmData.userId || envelope.recipientId === this.dmData.userId) {
try {
const dmMsg = await this.parseTextPayload(envelope, this.getMessages());
// Check if this is a confirmation of a message we sent
const isOurMessage = envelope.senderId !== this.dmData.userId;
if (isOurMessage) {
@@ -211,7 +211,7 @@ export class DMPanel extends MessagePanel {
}
}
}
this.addMessage(dmMsg);
// Update last read if it's from the other user
@@ -228,17 +228,17 @@ export class DMPanel extends MessagePanel {
try {
// Decrypt new content in-place
const plaintext = await decryptDm(
{
id,
senderId: 0,
recipientId: 0,
iv,
ciphertext,
salt,
iv2,
wrappedMk,
timestamp: new Date().toISOString()
},
{
id,
senderId: 0,
recipientId: 0,
iv,
ciphertext,
salt,
iv2,
wrappedMk,
timestamp: new Date().toISOString()
},
this.dmData.publicKey
);
let content = plaintext;
@@ -272,7 +272,7 @@ export class DMPanel extends MessagePanel {
if (this.dmData?.userId) {
onlineStatusManager.unsubscribe(this.dmData.userId);
}
this.dmData = null;
this.messagesLoaded = false;
this.clearMessages();
@@ -324,10 +324,10 @@ export class DMPanel extends MessagePanel {
async handleDeleteMessage(messageId: number): Promise<void> {
if (!this.currentUser.authToken || !this.dmData) return;
// Remove message immediately from UI
this.deleteMessageImmediately(messageId);
// Fire and forget server deletion; UI already updated
await deleteDmEnvelope(messageId, this.dmData.userId, this.currentUser.authToken);
}
@@ -348,14 +348,14 @@ export class DMPanel extends MessagePanel {
console.error("Failed to edit DM:", e);
});
}
async getProfile(): Promise<ProfileDialogData | null> {
if (!this.dmData || !this.currentUser.authToken) return null;
try {
const userProfile = await fetchUserProfile(this.currentUser.authToken, this.dmData.username);
if (!userProfile) return null;
return {
userId: userProfile.id,
username: userProfile.username,
@@ -373,10 +373,10 @@ export class DMPanel extends MessagePanel {
updateMessageReactions(dmEnvelopeId: number, reactions: any[]): void {
const messages = this.getMessages();
const messageIndex = messages.findIndex(msg =>
const messageIndex = messages.findIndex(msg =>
msg.runtimeData?.dmEnvelope?.id === dmEnvelopeId
);
if (messageIndex !== -1) {
const updatedMessage = { ...messages[messageIndex] };
updatedMessage.reactions = reactions;
@@ -89,7 +89,7 @@ export abstract class MessagePanel {
protected updateMessageReactions(messageId: number, reactions: any[]): void {
this.updateState({
messages: this.state.messages.map(msg =>
messages: this.state.messages.map(msg =>
msg.id === messageId ? { ...msg, reactions } : msg
)
});
@@ -125,7 +125,7 @@ export abstract class MessagePanel {
}
// ========== PUBLIC API ==========
// Event handlers
handleSendMessage(content: string, replyToId?: number, files: File[] = []): void {
this.sendMessageWithImmediateDisplay(content, replyToId, files);
@@ -136,10 +136,10 @@ export abstract class MessagePanel {
if (!message?.runtimeData?.sendingState?.retryData) return;
const { content, replyToId, files } = message.runtimeData.sendingState.retryData;
// Create new temp ID for retry
const tempId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
// Update status back to sending and create new temp message
const retryMessage: Message = {
...message,
@@ -184,7 +184,7 @@ export abstract class MessagePanel {
// Clear the timeout since we're handling the failure immediately
clearTimeout(timeoutId);
this.pendingMessages.delete(tempId);
// Update message to failed state directly
this.updateState({
messages: this.state.messages.map(msg => {
@@ -211,7 +211,7 @@ export abstract class MessagePanel {
if (pending) {
clearTimeout(pending.timeoutId);
this.pendingMessages.delete(tempId);
// Replace temporary message with confirmed one
this.updateState({
messages: this.state.messages.map(msg => {
@@ -249,7 +249,7 @@ export abstract class MessagePanel {
}
// ========== PRIVATE METHODS ==========
// Create and display message immediately with sending state
private async sendMessageWithImmediateDisplay(content: string, replyToId?: number, files: File[] = []): Promise<void> {
if (!content.trim() && files.length === 0) return;
@@ -326,7 +326,7 @@ export abstract class MessagePanel {
if (pending) {
clearTimeout(pending.timeoutId);
this.pendingMessages.delete(tempId);
// Update message to failed state
this.updateState({
messages: this.state.messages.map(msg => {
@@ -70,7 +70,7 @@ export class PublicChatPanel extends MessagePanel {
if (files.length === 0) {
const response = await request({
data: {
content: content.trim(),
content: content.trim(),
reply_to_id: replyToId ?? null
},
credentials: {
@@ -86,7 +86,7 @@ export class PublicChatPanel extends MessagePanel {
const form = new FormData();
form.append("payload", JSON.stringify({
content: content.trim(),
reply_to_id: replyToId ?? null
reply_to_id: replyToId ?? null
} satisfies SendMessageRequest["data"]));
for (const f of files) form.append("files", f, f.name);
const res = await fetch(`${API_BASE_URL}/send_message`, {
@@ -119,7 +119,7 @@ export class PublicChatPanel extends MessagePanel {
case 'newMessage':
if (response.data) {
const newMsg = response.data;
// Check if this is a confirmation of a message we sent
const isOurMessage = newMsg.username === this.currentUser.currentUser?.username;
if (isOurMessage) {
@@ -132,7 +132,7 @@ export class PublicChatPanel extends MessagePanel {
}
}
}
this.addMessage(newMsg);
}
break;
@@ -185,18 +185,18 @@ export class PublicChatPanel extends MessagePanel {
async handleDeleteMessage(id: number): Promise<void> {
// Remove message immediately from UI
this.deleteMessageImmediately(id);
// Fire and forget server deletion; UI already updated
await request({
type: "deleteMessage",
data: { message_id: id },
credentials: {
scheme: "Bearer",
credentials: this.currentUser.authToken!
credentials: {
scheme: "Bearer",
credentials: this.currentUser.authToken!
}
});
}
async getProfile(): Promise<ProfileDialogData | null> {
return {
username: "Общий чат",