Move toju-app into own its folder

This commit is contained in:
2026-03-29 23:30:37 +02:00
parent 0467a7b612
commit 8162e0444a
287 changed files with 42 additions and 34 deletions

View File

@@ -0,0 +1,31 @@
# Shared Kernel
Types and constants that are **intentionally shared** across multiple bounded
contexts (domains). Changing anything here affects every consumer, so changes
require coordination.
## Files
| File | Contents |
|---|---|
| `user.models.ts` | `User`, `UserStatus`, `UserRole`, `RoomMember` |
| `room.models.ts` | `Room`, `RoomSettings`, `RoomPermissions`, `Channel`, `ChannelType` |
| `message.models.ts` | `Message`, `Reaction`, `DELETED_MESSAGE_CONTENT` |
| `moderation.models.ts` | `BanEntry` |
| `voice-state.models.ts` | `VoiceState`, `ScreenShareState` |
| `chat-events.ts` | `ChatEventType`, `ChatEvent`, `ChatInventoryItem` |
| `media-preferences.ts` | `LatencyProfile`, `ScreenShareQuality`, quality presets |
| `signaling-contracts.ts` | `SignalingMessage`, `SignalingMessageType` |
| `attachment-contracts.ts` | `ChatAttachmentAnnouncement`, `ChatAttachmentMeta` |
## When to add here vs. in a domain
Add to shared-kernel when a type is referenced by **two or more domains** or by
**infrastructure + store**. If a type is only used inside one domain, keep it
in that domain's `domain/` folder.
## Backward compatibility
`core/models/index.ts` re-exports everything from this folder so existing
`import { X } from 'core/models'` lines keep working. **New code** should
import directly from `shared-kernel/` for clarity.

View File

@@ -0,0 +1,14 @@
export interface ChatAttachmentAnnouncement {
id: string;
filename: string;
size: number;
mime: string;
isImage: boolean;
uploaderPeerId?: string;
}
export interface ChatAttachmentMeta extends ChatAttachmentAnnouncement {
messageId: string;
filePath?: string;
savedPath?: string;
}

View File

@@ -0,0 +1,347 @@
import type { Message, Reaction } from './message.models';
import type { UserRole, RoomMember } from './user.models';
import type {
Room,
RoomSettings,
RoomPermissions,
Channel
} from './room.models';
import type { VoiceState } from './voice-state.models';
import type { BanEntry } from './moderation.models';
import type { ChatAttachmentAnnouncement, ChatAttachmentMeta } from './attachment-contracts';
export interface ChatInventoryItem {
id: string;
ts: number;
rc: number;
ac?: number;
}
// Every field that appears on any event is kept optional here so that
// code which accesses `event.roomId` etc. without narrowing first
// still compiles. Individual event interfaces below then make the
// relevant fields **required**.
export interface ChatEventBase {
fromPeerId?: string;
messageId?: string;
message?: Message;
reaction?: Reaction;
data?: string | Partial<Message>;
timestamp?: number;
targetUserId?: string;
roomId?: string;
items?: ChatInventoryItem[];
ids?: string[];
messages?: Message[];
attachments?: Record<string, ChatAttachmentMeta[]>;
total?: number;
index?: number;
count?: number;
lastUpdated?: number;
file?: ChatAttachmentAnnouncement;
fileId?: string;
hostId?: string;
hostOderId?: string;
previousHostId?: string;
previousHostOderId?: string;
kickedBy?: string;
bannedBy?: string;
content?: string;
editedAt?: number;
deletedAt?: number;
deletedBy?: string;
oderId?: string;
displayName?: string;
emoji?: string;
reason?: string;
settings?: Partial<RoomSettings>;
permissions?: Partial<RoomPermissions>;
voiceState?: Partial<VoiceState>;
isScreenSharing?: boolean;
icon?: string;
iconUpdatedAt?: number;
role?: UserRole;
room?: Partial<Room>;
channels?: Channel[];
members?: RoomMember[];
ban?: BanEntry;
bans?: BanEntry[];
banOderId?: string;
expiresAt?: number;
}
export interface ChatMessageEvent extends ChatEventBase {
type: 'message' | 'chat-message';
message: Message;
}
export interface MessageEditedEvent extends ChatEventBase {
type: 'edit' | 'message-edited';
messageId: string;
content: string;
editedAt: number;
}
export interface MessageDeletedEvent extends ChatEventBase {
type: 'delete' | 'message-deleted';
messageId: string;
}
export interface ReactionAddedEvent extends ChatEventBase {
type: 'reaction' | 'reaction-added';
messageId: string;
reaction: Reaction;
}
export interface ReactionRemovedEvent extends ChatEventBase {
type: 'reaction-removed';
messageId: string;
oderId: string;
emoji: string;
}
export interface FileAnnounceChatEvent extends ChatEventBase {
type: 'file-announce';
messageId: string;
file: ChatAttachmentAnnouncement;
}
export interface FileChunkChatEvent extends ChatEventBase {
type: 'file-chunk';
messageId: string;
fileId: string;
index: number;
total: number;
data: string;
}
export interface FileRequestChatEvent extends ChatEventBase {
type: 'file-request';
messageId: string;
fileId: string;
}
export interface FileCancelChatEvent extends ChatEventBase {
type: 'file-cancel';
messageId: string;
fileId: string;
}
export interface FileNotFoundChatEvent extends ChatEventBase {
type: 'file-not-found';
messageId: string;
fileId: string;
}
export interface ChatInventoryRequestEvent extends ChatEventBase {
type: 'chat-inventory-request';
roomId: string;
}
export interface ChatInventoryEvent extends ChatEventBase {
type: 'chat-inventory';
roomId: string;
items: ChatInventoryItem[];
total: number;
}
export interface ChatSyncRequestIdsEvent extends ChatEventBase {
type: 'chat-sync-request-ids';
roomId: string;
ids: string[];
}
export interface ChatSyncBatchEvent extends ChatEventBase {
type: 'chat-sync-batch';
roomId: string;
messages: Message[];
}
export interface ChatSyncSummaryEvent extends ChatEventBase {
type: 'chat-sync-summary';
}
export interface ChatSyncRequestEvent extends ChatEventBase {
type: 'chat-sync-request';
roomId: string;
}
export interface ChatSyncFullEvent extends ChatEventBase {
type: 'chat-sync-full';
roomId: string;
messages: Message[];
}
export interface RoomDeletedEvent extends ChatEventBase {
type: 'room-deleted';
roomId: string;
}
export interface RoomSettingsUpdateEvent extends ChatEventBase {
type: 'room-settings-update';
roomId: string;
settings: Partial<RoomSettings>;
}
export interface RoomPermissionsUpdateEvent extends ChatEventBase {
type: 'room-permissions-update';
roomId: string;
permissions?: Partial<RoomPermissions>;
}
export interface HostChangeEvent extends ChatEventBase {
type: 'host-change';
roomId: string;
hostId: string;
hostOderId: string;
previousHostId: string;
previousHostOderId: string;
}
export interface VoiceStateEvent extends ChatEventBase {
type: 'voice-state';
voiceState: Partial<VoiceState>;
}
export interface ScreenStateEvent extends ChatEventBase {
type: 'screen-state';
isScreenSharing: boolean;
}
export interface VoiceStateRequestEvent extends ChatEventBase {
type: 'voice-state-request';
}
export interface StateRequestEvent extends ChatEventBase {
type: 'state-request';
}
export interface ScreenShareRequestEvent extends ChatEventBase {
type: 'screen-share-request';
}
export interface ScreenShareStopEvent extends ChatEventBase {
type: 'screen-share-stop';
}
export interface ServerIconSummaryEvent extends ChatEventBase {
type: 'server-icon-summary';
roomId: string;
iconUpdatedAt: number;
}
export interface ServerIconRequestEvent extends ChatEventBase {
type: 'server-icon-request';
roomId: string;
}
export interface ServerIconFullEvent extends ChatEventBase {
type: 'server-icon-full';
roomId: string;
icon?: string;
iconUpdatedAt: number;
}
export interface ServerIconUpdateEvent extends ChatEventBase {
type: 'server-icon-update';
roomId: string;
icon: string;
iconUpdatedAt: number;
}
export interface ServerStateRequestEvent extends ChatEventBase {
type: 'server-state-request';
roomId: string;
}
export interface ServerStateFullEvent extends ChatEventBase {
type: 'server-state-full';
roomId: string;
room: Partial<Room>;
}
export interface MemberRosterRequestEvent extends ChatEventBase {
type: 'member-roster-request';
roomId: string;
}
export interface MemberRosterEvent extends ChatEventBase {
type: 'member-roster';
roomId: string;
members: RoomMember[];
}
export interface MemberLeaveEvent extends ChatEventBase {
type: 'member-leave';
roomId: string;
}
export interface RoleChangeEvent extends ChatEventBase {
type: 'role-change';
}
export interface KickEvent extends ChatEventBase {
type: 'kick';
}
export interface BanEvent extends ChatEventBase {
type: 'ban';
}
export interface UnbanEvent extends ChatEventBase {
type: 'unban';
}
export interface ChannelsUpdateEvent extends ChatEventBase {
type: 'channels-update';
roomId: string;
channels: Channel[];
}
/** Discriminated union of all P2P chat events. Narrow via `event.type`. */
export type ChatEvent =
| ChatMessageEvent
| MessageEditedEvent
| MessageDeletedEvent
| ReactionAddedEvent
| ReactionRemovedEvent
| FileAnnounceChatEvent
| FileChunkChatEvent
| FileRequestChatEvent
| FileCancelChatEvent
| FileNotFoundChatEvent
| ChatInventoryRequestEvent
| ChatInventoryEvent
| ChatSyncRequestIdsEvent
| ChatSyncBatchEvent
| ChatSyncSummaryEvent
| ChatSyncRequestEvent
| ChatSyncFullEvent
| RoomDeletedEvent
| RoomSettingsUpdateEvent
| RoomPermissionsUpdateEvent
| HostChangeEvent
| VoiceStateEvent
| ScreenStateEvent
| VoiceStateRequestEvent
| StateRequestEvent
| ScreenShareRequestEvent
| ScreenShareStopEvent
| ServerIconSummaryEvent
| ServerIconRequestEvent
| ServerIconFullEvent
| ServerIconUpdateEvent
| ServerStateRequestEvent
| ServerStateFullEvent
| MemberRosterRequestEvent
| MemberRosterEvent
| MemberLeaveEvent
| RoleChangeEvent
| KickEvent
| BanEvent
| UnbanEvent
| ChannelsUpdateEvent;
/** All possible `type` values, derived from the union. */
export type ChatEventType = ChatEvent['type'];

View File

@@ -0,0 +1,9 @@
export * from './user.models';
export * from './room.models';
export * from './message.models';
export * from './moderation.models';
export * from './voice-state.models';
export * from './chat-events';
export * from './media-preferences';
export * from './signaling-contracts';
export * from './attachment-contracts';

View File

@@ -0,0 +1,98 @@
export const LATENCY_PROFILES = [
'low',
'balanced',
'high'
] as const;
export type LatencyProfile = typeof LATENCY_PROFILES[number];
export const DEFAULT_LATENCY_PROFILE: LatencyProfile = 'balanced';
export const SCREEN_SHARE_QUALITIES = [
'performance',
'balanced',
'high-fps',
'quality'
] as const;
export type ScreenShareQuality = typeof SCREEN_SHARE_QUALITIES[number];
export const DEFAULT_SCREEN_SHARE_QUALITY: ScreenShareQuality = 'balanced';
export interface ScreenShareStartOptions {
includeSystemAudio: boolean;
quality: ScreenShareQuality;
}
export interface ScreenShareQualityPreset {
label: string;
description: string;
width: number;
height: number;
frameRate: number;
maxBitrateBps: number;
contentHint: 'motion' | 'detail';
degradationPreference: 'maintain-framerate' | 'maintain-resolution';
scaleResolutionDownBy?: number;
}
export const DEFAULT_SCREEN_SHARE_START_OPTIONS: ScreenShareStartOptions = {
includeSystemAudio: false,
quality: DEFAULT_SCREEN_SHARE_QUALITY
};
export const SCREEN_SHARE_QUALITY_PRESETS: Record<ScreenShareQuality, ScreenShareQualityPreset> = {
performance: {
label: 'Performance saver',
description: '720p / 30 FPS with lower CPU and bandwidth usage.',
width: 1280,
height: 720,
frameRate: 30,
maxBitrateBps: 2_000_000,
contentHint: 'motion',
degradationPreference: 'maintain-framerate',
scaleResolutionDownBy: 1
},
balanced: {
label: 'Balanced',
description: '1080p / 30 FPS for stable quality in most cases.',
width: 1920,
height: 1080,
frameRate: 30,
maxBitrateBps: 4_000_000,
contentHint: 'detail',
degradationPreference: 'maintain-resolution',
scaleResolutionDownBy: 1
},
'high-fps': {
label: 'High FPS',
description: '1080p / 60 FPS for games and fast motion.',
width: 1920,
height: 1080,
frameRate: 60,
maxBitrateBps: 6_000_000,
contentHint: 'motion',
degradationPreference: 'maintain-framerate',
scaleResolutionDownBy: 1
},
quality: {
label: 'Sharp text',
description: '1440p / 30 FPS for detailed UI and text clarity.',
width: 2560,
height: 1440,
frameRate: 30,
maxBitrateBps: 8_000_000,
contentHint: 'detail',
degradationPreference: 'maintain-resolution',
scaleResolutionDownBy: 1
}
};
export const SCREEN_SHARE_QUALITY_OPTIONS = (
Object.entries(SCREEN_SHARE_QUALITY_PRESETS) as [ScreenShareQuality, ScreenShareQualityPreset][]
).map(([id, preset]) => ({
id,
...preset
}));
export const ELECTRON_ENTIRE_SCREEN_SOURCE_NAME = 'Entire Screen';

View File

@@ -0,0 +1,24 @@
export const DELETED_MESSAGE_CONTENT = '[Message deleted]';
export interface Message {
id: string;
roomId: string;
channelId?: string;
senderId: string;
senderName: string;
content: string;
timestamp: number;
editedAt?: number;
reactions: Reaction[];
isDeleted: boolean;
replyToId?: string;
}
export interface Reaction {
id: string;
messageId: string;
oderId: string;
userId: string;
emoji: string;
timestamp: number;
}

View File

@@ -0,0 +1,10 @@
export interface BanEntry {
oderId: string;
userId: string;
roomId: string;
bannedBy: string;
displayName?: string;
reason?: string;
expiresAt?: number;
timestamp: number;
}

View File

@@ -0,0 +1,54 @@
import type { RoomMember } from './user.models';
export type ChannelType = 'text' | 'voice';
export interface Channel {
id: string;
name: string;
type: ChannelType;
position: number;
}
export interface Room {
id: string;
name: string;
description?: string;
topic?: string;
hostId: string;
password?: string;
hasPassword?: boolean;
isPrivate: boolean;
createdAt: number;
userCount: number;
maxUsers?: number;
icon?: string;
iconUpdatedAt?: number;
permissions?: RoomPermissions;
channels?: Channel[];
members?: RoomMember[];
sourceId?: string;
sourceName?: string;
sourceUrl?: string;
}
export interface RoomSettings {
name: string;
description?: string;
topic?: string;
isPrivate: boolean;
password?: string;
hasPassword?: boolean;
maxUsers?: number;
rules?: string[];
}
export interface RoomPermissions {
adminsManageRooms?: boolean;
moderatorsManageRooms?: boolean;
adminsManageIcon?: boolean;
moderatorsManageIcon?: boolean;
allowVoice?: boolean;
allowScreenShare?: boolean;
allowFileUploads?: boolean;
slowModeInterval?: number;
}

View File

@@ -0,0 +1,20 @@
export type SignalingMessageType =
| 'offer'
| 'answer'
| 'ice-candidate'
| 'join'
| 'leave'
| 'chat'
| 'state-sync'
| 'kick'
| 'ban'
| 'host-change'
| 'room-update';
export interface SignalingMessage {
type: SignalingMessageType;
from: string;
to?: string;
payload: unknown;
timestamp: number;
}

View File

@@ -0,0 +1,33 @@
import type { VoiceState, ScreenShareState } from './voice-state.models';
export type UserStatus = 'online' | 'away' | 'busy' | 'offline';
export type UserRole = 'host' | 'admin' | 'moderator' | 'member';
export interface User {
id: string;
oderId: string;
username: string;
displayName: string;
avatarUrl?: string;
status: UserStatus;
role: UserRole;
joinedAt: number;
peerId?: string;
isOnline?: boolean;
isAdmin?: boolean;
isRoomOwner?: boolean;
voiceState?: VoiceState;
screenShareState?: ScreenShareState;
}
export interface RoomMember {
id: string;
oderId?: string;
username: string;
displayName: string;
avatarUrl?: string;
role: UserRole;
joinedAt: number;
lastSeenAt: number;
}

View File

@@ -0,0 +1,17 @@
export interface VoiceState {
isConnected: boolean;
isMuted: boolean;
isDeafened: boolean;
isSpeaking: boolean;
isMutedByAdmin?: boolean;
volume?: number;
roomId?: string;
serverId?: string;
}
export interface ScreenShareState {
isSharing: boolean;
streamId?: string;
sourceId?: string;
sourceName?: string;
}