feat(ws-client): add TypingStart/TypingStop protocol types and client handlers
ws-protocol.ts: TypingStartPayload/TypingStopPayload interfaces, WsEventPayload union types. room-ws-client.ts: onTypingStart/onTypingStop callbacks, sendTyping() method, event dispatch for typing.start/typing_start. editor/types.ts: special_here/special_channel MentionType + description field on MentionItem.
This commit is contained in:
parent
5776af18ca
commit
59640c6f44
@ -2,13 +2,14 @@
|
|||||||
* Core types for the IM editor (mentions, files, emojis).
|
* Core types for the IM editor (mentions, files, emojis).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type MentionType = 'user' | 'channel' | 'ai' | 'command';
|
export type MentionType = 'user' | 'channel' | 'ai' | 'command' | 'special_here' | 'special_channel';
|
||||||
|
|
||||||
export interface MentionItem {
|
export interface MentionItem {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: MentionType;
|
type: MentionType;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
description?: string; // shown under label in suggestion dropdown
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileData {
|
export interface FileData {
|
||||||
|
|||||||
@ -78,6 +78,8 @@ export interface RoomWsCallbacks {
|
|||||||
onMessagePinned?: (payload: import('./ws-protocol').MessagePinnedPayload) => void;
|
onMessagePinned?: (payload: import('./ws-protocol').MessagePinnedPayload) => void;
|
||||||
onMessageUnpinned?: (payload: import('./ws-protocol').MessageUnpinnedPayload) => void;
|
onMessageUnpinned?: (payload: import('./ws-protocol').MessageUnpinnedPayload) => void;
|
||||||
onUserPresence?: (payload: UserPresencePayload) => void;
|
onUserPresence?: (payload: UserPresencePayload) => void;
|
||||||
|
onTypingStart?: (payload: import('./ws-protocol').TypingStartPayload) => void;
|
||||||
|
onTypingStop?: (payload: import('./ws-protocol').TypingStopPayload) => void;
|
||||||
onStatusChange?: (status: RoomWsStatus) => void;
|
onStatusChange?: (status: RoomWsStatus) => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
/** Called each time the client sends a heartbeat ping */
|
/** Called each time the client sends a heartbeat ping */
|
||||||
@ -961,6 +963,14 @@ export class RoomWsClient {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Send a typing_start / typing_stop event directly via WebSocket push (no response needed). */
|
||||||
|
sendTyping(roomId: string, action: 'start' | 'stop'): void {
|
||||||
|
if (this.ws && this.status === 'open') {
|
||||||
|
const event = { type: 'event', event: `typing_${action}`, room_id: roomId };
|
||||||
|
this.ws.send(JSON.stringify(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private handleMessage(rawText: string): void {
|
private handleMessage(rawText: string): void {
|
||||||
// Handle raw JSON pong before full parsing — resets heartbeat
|
// Handle raw JSON pong before full parsing — resets heartbeat
|
||||||
if (rawText.trim() === '{"type":"pong"}') {
|
if (rawText.trim() === '{"type":"pong"}') {
|
||||||
@ -1033,6 +1043,22 @@ export class RoomWsClient {
|
|||||||
status: ((event.data as { status?: string })?.status ?? 'offline') as 'online' | 'away' | 'dnd' | 'offline',
|
status: ((event.data as { status?: string })?.status ?? 'offline') as 'online' | 'away' | 'dnd' | 'offline',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'typing.start':
|
||||||
|
case 'typing_start':
|
||||||
|
this.callbacks.onTypingStart?.({
|
||||||
|
room_id: event.room_id ?? '',
|
||||||
|
user_id: (event.data as { user_id?: string })?.user_id ?? '',
|
||||||
|
username: (event.data as { username?: string })?.username ?? '',
|
||||||
|
avatar_url: (event.data as { avatar_url?: string })?.avatar_url,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'typing.stop':
|
||||||
|
case 'typing_stop':
|
||||||
|
this.callbacks.onTypingStop?.({
|
||||||
|
room_id: event.room_id ?? '',
|
||||||
|
user_id: (event.data as { user_id?: string })?.user_id ?? '',
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Unknown event type - ignore silently
|
// Unknown event type - ignore silently
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -133,7 +133,20 @@ export type WsResponseData =
|
|||||||
| NotificationListData
|
| NotificationListData
|
||||||
| MentionListData
|
| MentionListData
|
||||||
| SubscribeData
|
| SubscribeData
|
||||||
| UserInfo[];
|
| UserInfo[]
|
||||||
|
| null;
|
||||||
|
|
||||||
|
export interface TypingStartPayload {
|
||||||
|
room_id: string;
|
||||||
|
user_id: string;
|
||||||
|
username: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypingStopPayload {
|
||||||
|
room_id: string;
|
||||||
|
user_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WsEvent {
|
export interface WsEvent {
|
||||||
type: 'event';
|
type: 'event';
|
||||||
@ -155,6 +168,8 @@ export type WsEventPayload =
|
|||||||
| { type: 'message_pinned'; data: MessagePinnedPayload }
|
| { type: 'message_pinned'; data: MessagePinnedPayload }
|
||||||
| { type: 'message_unpinned'; data: MessageUnpinnedPayload }
|
| { type: 'message_unpinned'; data: MessageUnpinnedPayload }
|
||||||
| { type: 'user_presence'; data: UserPresencePayload }
|
| { type: 'user_presence'; data: UserPresencePayload }
|
||||||
|
| { type: 'typing_start'; data: TypingStartPayload }
|
||||||
|
| { type: 'typing_stop'; data: TypingStopPayload }
|
||||||
| { type: string; data: unknown }; // catch-all for unknown events
|
| { type: string; data: unknown }; // catch-all for unknown events
|
||||||
|
|
||||||
export interface RoomMessagePayload {
|
export interface RoomMessagePayload {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user