Add debugging console

This commit is contained in:
2026-03-07 21:59:39 +01:00
parent 66246e4e16
commit 90f067e662
49 changed files with 5962 additions and 139 deletions

View File

@@ -18,7 +18,9 @@ import {
import { mergeMap } from 'rxjs/operators';
import { Action } from '@ngrx/store';
import { Message } from '../../core/models/index';
import type { DebuggingService } from '../../core/services';
import { DatabaseService } from '../../core/services/database.service';
import { trackDebuggingTaskFailure } from '../../core/helpers/debugging-helpers';
import { WebRTCService } from '../../core/services/webrtc.service';
import { AttachmentService } from '../../core/services/attachment.service';
import { MessagesActions } from './messages.actions';
@@ -39,6 +41,7 @@ export interface IncomingMessageContext {
db: DatabaseService;
webrtc: WebRTCService;
attachments: AttachmentService;
debugging: DebuggingService;
currentUser: any;
currentRoom: any;
}
@@ -256,7 +259,7 @@ function requestMissingImages(
/** Saves an incoming chat message to DB and dispatches receiveMessage. */
function handleChatMessage(
event: any,
{ db, currentUser }: IncomingMessageContext
{ db, debugging, currentUser }: IncomingMessageContext
): Observable<Action> {
const msg = event.message;
@@ -271,22 +274,43 @@ function handleChatMessage(
if (isOwnMessage)
return EMPTY;
db.saveMessage(msg);
trackBackgroundOperation(
db.saveMessage(msg),
debugging,
'Failed to persist incoming chat message',
{
channelId: msg.channelId || 'general',
fromPeerId: event.fromPeerId ?? null,
messageId: msg.id,
roomId: msg.roomId,
senderId: msg.senderId
}
);
return of(MessagesActions.receiveMessage({ message: msg }));
}
/** Applies a remote message edit to the local DB and store. */
function handleMessageEdited(
event: any,
{ db }: IncomingMessageContext
{ db, debugging }: IncomingMessageContext
): Observable<Action> {
if (!event.messageId || !event.content)
return EMPTY;
db.updateMessage(event.messageId, {
content: event.content,
editedAt: event.editedAt
});
trackBackgroundOperation(
db.updateMessage(event.messageId, {
content: event.content,
editedAt: event.editedAt
}),
debugging,
'Failed to persist incoming message edit',
{
editedAt: event.editedAt ?? null,
fromPeerId: event.fromPeerId ?? null,
messageId: event.messageId
}
);
return of(
MessagesActions.editMessageSuccess({
@@ -300,12 +324,22 @@ function handleMessageEdited(
/** Applies a remote message deletion to the local DB and store. */
function handleMessageDeleted(
event: any,
{ db }: IncomingMessageContext
{ db, debugging }: IncomingMessageContext
): Observable<Action> {
if (!event.messageId)
return EMPTY;
db.deleteMessage(event.messageId);
trackBackgroundOperation(
db.deleteMessage(event.messageId),
debugging,
'Failed to persist incoming message deletion',
{
deletedBy: event.deletedBy ?? null,
fromPeerId: event.fromPeerId ?? null,
messageId: event.messageId
}
);
return of(
MessagesActions.deleteMessageSuccess({ messageId: event.messageId })
);
@@ -314,24 +348,46 @@ function handleMessageDeleted(
/** Saves an incoming reaction to DB and updates the store. */
function handleReactionAdded(
event: any,
{ db }: IncomingMessageContext
{ db, debugging }: IncomingMessageContext
): Observable<Action> {
if (!event.messageId || !event.reaction)
return EMPTY;
db.saveReaction(event.reaction);
trackBackgroundOperation(
db.saveReaction(event.reaction),
debugging,
'Failed to persist incoming reaction',
{
emoji: event.reaction.emoji,
fromPeerId: event.fromPeerId ?? null,
messageId: event.messageId,
reactionId: event.reaction.id
}
);
return of(MessagesActions.addReactionSuccess({ reaction: event.reaction }));
}
/** Removes a reaction from DB and updates the store. */
function handleReactionRemoved(
event: any,
{ db }: IncomingMessageContext
{ db, debugging }: IncomingMessageContext
): Observable<Action> {
if (!event.messageId || !event.oderId || !event.emoji)
return EMPTY;
db.removeReaction(event.messageId, event.oderId, event.emoji);
trackBackgroundOperation(
db.removeReaction(event.messageId, event.oderId, event.emoji),
debugging,
'Failed to persist incoming reaction removal',
{
emoji: event.emoji,
fromPeerId: event.fromPeerId ?? null,
messageId: event.messageId,
oderId: event.oderId
}
);
return of(
MessagesActions.removeReactionSuccess({
messageId: event.messageId,
@@ -442,12 +498,24 @@ function handleSyncRequest(
/** Merges a full message dump from a peer into the local DB and store. */
function handleSyncFull(
event: any,
{ db }: IncomingMessageContext
{ db, debugging }: IncomingMessageContext
): Observable<Action> {
if (!event.messages || !Array.isArray(event.messages))
return EMPTY;
event.messages.forEach((msg: Message) => db.saveMessage(msg));
event.messages.forEach((msg: Message) => {
trackBackgroundOperation(
db.saveMessage(msg),
debugging,
'Failed to persist full-sync message batch item',
{
fromPeerId: event.fromPeerId ?? null,
messageId: msg.id,
roomId: msg.roomId
}
);
});
return of(MessagesActions.syncMessages({ messages: event.messages }));
}
@@ -493,3 +561,12 @@ export function dispatchIncomingMessage(
return handler ? handler(event, ctx) : EMPTY;
}
function trackBackgroundOperation(
task: Promise<unknown> | unknown,
debugging: DebuggingService,
message: string,
payload: Record<string, unknown>
): void {
trackDebuggingTaskFailure(task, debugging, 'messages', message, payload);
}

View File

@@ -40,6 +40,7 @@ import { RoomsActions } from '../rooms/rooms.actions';
import { selectMessagesSyncing } from './messages.selectors';
import { selectCurrentRoom } from '../rooms/rooms.selectors';
import { DatabaseService } from '../../core/services/database.service';
import { DebuggingService } from '../../core/services/debugging.service';
import { WebRTCService } from '../../core/services/webrtc.service';
import {
INVENTORY_LIMIT,
@@ -55,6 +56,7 @@ export class MessagesSyncEffects {
private readonly actions$ = inject(Actions);
private readonly store = inject(Store);
private readonly db = inject(DatabaseService);
private readonly debugging = inject(DebuggingService);
private readonly webrtc = inject(WebRTCService);
/** Tracks whether the last sync cycle found no new messages. */
@@ -135,8 +137,12 @@ export class MessagesSyncEffects {
type: 'chat-inventory-request',
roomId: activeRoom.id
} as any);
} catch {
/* peer may have disconnected */
} catch (error) {
this.debugging.warn('messages', 'Failed to kick off room sync for peer', {
error,
peerId: pid,
roomId: activeRoom.id
});
}
}
})
@@ -181,15 +187,24 @@ export class MessagesSyncEffects {
type: 'chat-inventory-request',
roomId: room.id
} as any);
} catch {
/* peer may have disconnected */
} catch (error) {
this.debugging.warn('messages', 'Failed to request peer inventory during sync poll', {
error,
peerId: pid,
roomId: room.id
});
}
}
return MessagesActions.startSync();
}),
catchError(() => {
catchError((error) => {
this.lastSyncClean = false;
this.debugging.warn('messages', 'Periodic sync poll failed', {
error,
roomId: room.id
});
return of(MessagesActions.syncComplete());
})
);

View File

@@ -32,6 +32,8 @@ import { MessagesActions } from './messages.actions';
import { selectCurrentUser } from '../users/users.selectors';
import { selectCurrentRoom } from '../rooms/rooms.selectors';
import { DatabaseService } from '../../core/services/database.service';
import { reportDebuggingError, trackDebuggingTaskFailure } from '../../core/helpers/debugging-helpers';
import { DebuggingService } from '../../core/services';
import { WebRTCService } from '../../core/services/webrtc.service';
import { TimeSyncService } from '../../core/services/time-sync.service';
import { AttachmentService } from '../../core/services/attachment.service';
@@ -44,6 +46,7 @@ export class MessagesEffects {
private readonly actions$ = inject(Actions);
private readonly store = inject(Store);
private readonly db = inject(DatabaseService);
private readonly debugging = inject(DebuggingService);
private readonly webrtc = inject(WebRTCService);
private readonly timeSync = inject(TimeSyncService);
private readonly attachments = inject(AttachmentService);
@@ -97,7 +100,17 @@ export class MessagesEffects {
replyToId
};
this.db.saveMessage(message);
this.trackBackgroundOperation(
this.db.saveMessage(message),
'Failed to persist outgoing chat message',
{
channelId: message.channelId,
contentLength: message.content.length,
messageId: message.id,
roomId: message.roomId
}
);
this.webrtc.broadcastMessage({ type: 'chat-message',
message });
@@ -131,8 +144,16 @@ export class MessagesEffects {
const editedAt = this.timeSync.now();
this.db.updateMessage(messageId, { content,
editedAt });
this.trackBackgroundOperation(
this.db.updateMessage(messageId, { content,
editedAt }),
'Failed to persist edited chat message',
{
contentLength: content.length,
editedAt,
messageId
}
);
this.webrtc.broadcastMessage({ type: 'message-edited',
messageId,
@@ -171,7 +192,12 @@ export class MessagesEffects {
return of(MessagesActions.deleteMessageFailure({ error: 'Cannot delete others messages' }));
}
this.db.updateMessage(messageId, { isDeleted: true });
this.trackBackgroundOperation(
this.db.updateMessage(messageId, { isDeleted: true }),
'Failed to persist message deletion',
{ messageId }
);
this.webrtc.broadcastMessage({ type: 'message-deleted',
messageId });
@@ -204,7 +230,15 @@ export class MessagesEffects {
return of(MessagesActions.deleteMessageFailure({ error: 'Permission denied' }));
}
this.db.updateMessage(messageId, { isDeleted: true });
this.trackBackgroundOperation(
this.db.updateMessage(messageId, { isDeleted: true }),
'Failed to persist admin message deletion',
{
deletedBy: currentUser.id,
messageId
}
);
this.webrtc.broadcastMessage({ type: 'message-deleted',
messageId,
deletedBy: currentUser.id });
@@ -235,7 +269,17 @@ export class MessagesEffects {
timestamp: this.timeSync.now()
};
this.db.saveReaction(reaction);
this.trackBackgroundOperation(
this.db.saveReaction(reaction),
'Failed to persist reaction',
{
emoji,
messageId,
reactionId: reaction.id,
userId: currentUser.id
}
);
this.webrtc.broadcastMessage({ type: 'reaction-added',
messageId,
reaction });
@@ -254,7 +298,16 @@ export class MessagesEffects {
if (!currentUser)
return EMPTY;
this.db.removeReaction(messageId, currentUser.id, emoji);
this.trackBackgroundOperation(
this.db.removeReaction(messageId, currentUser.id, emoji),
'Failed to persist reaction removal',
{
emoji,
messageId,
userId: currentUser.id
}
);
this.webrtc.broadcastMessage({
type: 'reaction-removed',
messageId,
@@ -286,18 +339,43 @@ export class MessagesEffects {
mergeMap(([
event,
currentUser,
currentRoom]: [any, any, any
currentRoom
]) => {
const ctx: IncomingMessageContext = {
db: this.db,
webrtc: this.webrtc,
attachments: this.attachments,
debugging: this.debugging,
currentUser,
currentRoom
};
return dispatchIncomingMessage(event, ctx);
return dispatchIncomingMessage(event, ctx).pipe(
catchError((error) => {
const eventRecord = event as unknown as Record<string, unknown>;
const messageRecord = (eventRecord['message'] && typeof eventRecord['message'] === 'object' && !Array.isArray(eventRecord['message']))
? eventRecord['message'] as Record<string, unknown>
: null;
reportDebuggingError(this.debugging, 'messages', 'Failed to process incoming peer message', {
eventType: typeof eventRecord['type'] === 'string' ? eventRecord['type'] : 'unknown',
fromPeerId: typeof eventRecord['fromPeerId'] === 'string' ? eventRecord['fromPeerId'] : null,
messageId: typeof eventRecord['messageId'] === 'string'
? eventRecord['messageId']
: (typeof messageRecord?.['id'] === 'string' ? messageRecord['id'] : null),
roomId: typeof eventRecord['roomId'] === 'string'
? eventRecord['roomId']
: (typeof messageRecord?.['roomId'] === 'string' ? messageRecord['roomId'] : null)
}, error);
return EMPTY;
})
);
})
)
);
private trackBackgroundOperation(task: Promise<unknown> | unknown, message: string, payload: Record<string, unknown>): void {
trackDebuggingTaskFailure(task, this.debugging, 'messages', message, payload);
}
}