feat: add Activity type system, DB migration, and self-only showActivity in sanitizeUser
- Add Activity, ActivityType, ActivityTimestamps, ActivityAssets types to shared types - Add activity_update client event and activities field on presence_update server event - Add userActivities to ready payload and showActivity to User/UpdateUserRequest - Create shared activities.ts with ACTIVITY_LIMITS, ACTIVITY_PRIORITY, getPrimaryActivity - Add show_activity column to users table (schema + migration) - Update sanitizeUser with isSelf parameter; only include showActivity for self - Fix .map(sanitizeUser) calls to use arrow wrapper to prevent index-as-boolean bug - Mark auth routes (register/login) as isSelf=true since they return own user data
This commit is contained in:
@@ -8,7 +8,9 @@
|
||||
"exports": {
|
||||
".": "./src/types.ts",
|
||||
"./src/permissions": "./src/permissions.ts",
|
||||
"./src/permissions.js": "./src/permissions.ts"
|
||||
"./src/permissions.js": "./src/permissions.ts",
|
||||
"./src/activities": "./src/activities.ts",
|
||||
"./src/activities.js": "./src/activities.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Activity, ActivityType } from './types.js';
|
||||
|
||||
export const ACTIVITY_LIMITS = {
|
||||
MAX_ACTIVITIES_PER_USER: 5,
|
||||
MAX_NAME_LENGTH: 128,
|
||||
MAX_DETAILS_LENGTH: 128,
|
||||
MAX_STATE_LENGTH: 128,
|
||||
MAX_ASSET_TEXT_LENGTH: 128,
|
||||
MAX_URL_LENGTH: 512,
|
||||
} as const;
|
||||
|
||||
export const ACTIVITY_PRIORITY: Record<ActivityType, number> = {
|
||||
streaming: 5,
|
||||
playing: 4,
|
||||
listening: 3,
|
||||
watching: 2,
|
||||
custom: 1,
|
||||
};
|
||||
|
||||
export function getPrimaryActivity(activities: Activity[]): Activity | null {
|
||||
if (!activities.length) return null;
|
||||
return activities.reduce((best, current) =>
|
||||
ACTIVITY_PRIORITY[current.type] > ACTIVITY_PRIORITY[best.type] ? current : best
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export interface User {
|
||||
homeInstance: string | null;
|
||||
homeUserId: string | null;
|
||||
replicatedInstances: ReplicatedInstance[];
|
||||
showActivity?: boolean;
|
||||
}
|
||||
|
||||
export interface ReplicatedInstance {
|
||||
@@ -281,6 +282,32 @@ export interface DmMessageWithUser extends DmMessage {
|
||||
replyTo?: DmMessageWithUser | null;
|
||||
}
|
||||
|
||||
// ─── Activity Types ────────────────────────────────────────────────────────
|
||||
|
||||
export type ActivityType = 'custom' | 'playing' | 'listening' | 'watching' | 'streaming';
|
||||
|
||||
export interface ActivityTimestamps {
|
||||
start?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
export interface ActivityAssets {
|
||||
largeImage?: string;
|
||||
largeText?: string;
|
||||
smallImage?: string;
|
||||
smallText?: string;
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
type: ActivityType;
|
||||
name: string;
|
||||
details?: string;
|
||||
state?: string;
|
||||
timestamps?: ActivityTimestamps;
|
||||
assets?: ActivityAssets;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
// ─── WebSocket Event Types ──────────────────────────────────────────────────
|
||||
|
||||
// Client → Server Events
|
||||
@@ -310,16 +337,17 @@ export type ClientEvent =
|
||||
| { type: 'voice_space_deafen'; userId: string; deafened: boolean }
|
||||
| { type: 'voice_move'; userId: string; targetChannelId: string }
|
||||
| { type: 'voice_disconnect'; userId: string }
|
||||
| { type: 'activity_update'; activities: Activity[] }
|
||||
| { type: 'ping' };
|
||||
|
||||
// Server → Client Events
|
||||
export type ServerEvent =
|
||||
| { type: 'ready'; user: User; spaces: SpaceWithChannelsAndMembers[]; dmChannels: DmChannel[]; folders?: SpaceFolder[]; spaceLayout?: SpaceLayoutItem[] | null; layoutUpdatedAt?: number; voiceStates?: Record<string, string[]>; voiceUserStates?: Record<string, { isMuted: boolean; isDeafened: boolean; isCameraOn: boolean; isScreenSharing: boolean }>; readStates?: ReadState[]; activeCalls?: ActiveCallInfo[]; spaceVoiceStates?: Record<string, { spaceMuted: boolean; spaceDeafened: boolean }> }
|
||||
| { type: 'ready'; user: User; spaces: SpaceWithChannelsAndMembers[]; dmChannels: DmChannel[]; folders?: SpaceFolder[]; spaceLayout?: SpaceLayoutItem[] | null; layoutUpdatedAt?: number; voiceStates?: Record<string, string[]>; voiceUserStates?: Record<string, { isMuted: boolean; isDeafened: boolean; isCameraOn: boolean; isScreenSharing: boolean }>; readStates?: ReadState[]; activeCalls?: ActiveCallInfo[]; spaceVoiceStates?: Record<string, { spaceMuted: boolean; spaceDeafened: boolean }>; userActivities?: Record<string, Activity[]> }
|
||||
| { type: 'message_created'; message: MessageWithUser }
|
||||
| { type: 'message_updated'; message: MessageWithUser }
|
||||
| { type: 'message_deleted'; messageId: string; channelId: string }
|
||||
| { type: 'typing'; channelId: string; userId: string; username: string }
|
||||
| { type: 'presence_update'; userId: string; status: string }
|
||||
| { type: 'presence_update'; userId: string; status: string; activities?: Activity[] }
|
||||
| { type: 'voice_state_update'; channelId: string; userId: string; action: 'join' | 'leave' }
|
||||
| { type: 'member_joined'; spaceId: string; member: MemberWithUser }
|
||||
| { type: 'member_left'; spaceId: string; userId: string }
|
||||
@@ -435,6 +463,7 @@ export interface UpdateUserRequest {
|
||||
homeUserId?: string;
|
||||
profileUpdatedAt?: number;
|
||||
discoverable?: boolean;
|
||||
showActivity?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateMemberRequest {
|
||||
|
||||
Reference in New Issue
Block a user