Skip to content

Commit

Permalink
Move DMs to websockets
Browse files Browse the repository at this point in the history
  • Loading branch information
IanPhilips committed Jul 16, 2024
1 parent e1150da commit 31f8b69
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 52 deletions.
5 changes: 4 additions & 1 deletion backend/api/src/create-private-user-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { HOUR_MS } from 'common/util/time'
import { JSONContent } from '@tiptap/core'
import { ChatVisibility } from 'common/chat-message'
import { track } from 'shared/analytics'
import { broadcast } from 'shared/websockets/server'
dayjs.extend(utc)
dayjs.extend(timezone)

Expand Down Expand Up @@ -89,7 +90,9 @@ export const createPrivateUserMessageMain = async (
[channelId, creator.id],
(r) => r.user_id
)

otherUserIds.concat(creator.id).forEach((otherUserId) => {
broadcast(`private-user-messages/${otherUserId}`, {})
})
let bothHaveLoverProfiles = false
const hasLoverProfile = await pg.oneOrNone(
'select 1 from lovers where user_id = $1',
Expand Down
8 changes: 5 additions & 3 deletions backend/api/src/get-private-messages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { APIHandler } from './helpers/endpoint'
import { PrivateMessageChannel } from 'common/supabase/private-messages'
import { Row } from 'common/supabase/utils'
import {
convertPrivateChatMessage,
PrivateMessageChannel,
} from 'common/supabase/private-messages'
import { groupBy, mapValues } from 'lodash'

export const getChannelMemberships: APIHandler<
Expand Down Expand Up @@ -101,7 +103,7 @@ export const getChannelMessages: APIHandler<'get-channel-messages'> = async (
limit $3
`,
[channelId, auth.uid, limit, id],
(r) => r as Row<'private_user_messages'>
convertPrivateChatMessage
)
}

Expand Down
4 changes: 2 additions & 2 deletions common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { Row } from 'common/supabase/utils'
import { LikeData, ShipData } from './love-types'
import { AnyBalanceChangeType } from 'common/balance-change'
import { Dashboard } from 'common/dashboard'
import { ChatMessage } from 'common/chat-message'
import { ChatMessage, PrivateChatMessage } from 'common/chat-message'
import { PrivateUser, User } from 'common/user'
import { ManaSupply } from 'common/stats'
import { Repost } from 'common/repost'
Expand Down Expand Up @@ -1269,7 +1269,7 @@ export const API = (_apiTypeCheck = {
limit: z.coerce.number(),
id: z.coerce.number().optional(),
}),
returns: [] as Row<'private_user_messages'>[],
returns: [] as PrivateChatMessage[],
},
'get-channel-seen-time': {
method: 'GET',
Expand Down
3 changes: 3 additions & 0 deletions common/src/chat-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export type ChatMessage = {
createdTime: number
visibility: ChatVisibility
}
export type PrivateChatMessage = Omit<ChatMessage, 'id'> & {
id: number
}

export const convertPublicChatMessage = (row: Row<'chat_messages'>) =>
convertSQLtoTS<'chat_messages', ChatMessage>(row, {
Expand Down
7 changes: 6 additions & 1 deletion common/src/supabase/private-messages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { convertSQLtoTS, Row, tsToMillis } from 'common/supabase/utils'
import { ChatMessage } from 'common/chat-message'
import { ChatMessage, PrivateChatMessage } from 'common/chat-message'

export type PrivateMessageChannel = {
channel_id: number
Expand All @@ -12,3 +12,8 @@ export const convertChatMessage = (row: Row<'private_user_messages'>) =>
convertSQLtoTS<'private_user_messages', ChatMessage>(row, {
created_time: tsToMillis as any,
})

export const convertPrivateChatMessage = (row: Row<'private_user_messages'>) =>
convertSQLtoTS<'private_user_messages', PrivateChatMessage>(row, {
created_time: tsToMillis as any,
})
90 changes: 52 additions & 38 deletions web/hooks/use-private-messages.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,76 @@
import { ChatMessage } from 'common/chat-message'
import { millisToTs, Row, tsToMillis } from 'common/supabase/utils'
import { PrivateChatMessage } from 'common/chat-message'
import { millisToTs, tsToMillis } from 'common/supabase/utils'
import { useEffect } from 'react'
import { first, last, orderBy, sortBy, uniq } from 'lodash'
import { first, orderBy, uniq, uniqBy } from 'lodash'
import { usePersistentLocalState } from 'web/hooks/use-persistent-local-state'
import {
getSortedChatMessageChannels,
getTotalChatMessages,
} from 'web/lib/supabase/private-messages'
import { usePersistentApiPolling } from 'web/hooks/use-persistent-supabase-polling'
import { useIsPageVisible } from 'web/hooks/use-page-visible'
import { track } from 'web/lib/service/analytics'
import { usePathname } from 'next/navigation'
import { api } from 'web/lib/api/api'
import {
convertChatMessage,
PrivateMessageChannel,
} from 'common/supabase/private-messages'
import { PrivateMessageChannel } from 'common/supabase/private-messages'
import { useAPIGetter } from 'web/hooks/use-api-getter'
import { useApiSubscription } from 'web/hooks/use-api-subscription'

// NOTE: must be authorized (useIsAuthorized) to use this hook
export function useRealtimePrivateMessagesPolling(
export function usePrivateMessages(
channelId: number,
ms: number,
initialLimit = 50
limit: number,
userId: string
) {
const allRowsQ = api('get-channel-messages', {
channelId,
limit: initialLimit,
}) as any
const newRowsOnlyQ = (rows: Row<'private_user_messages'>[] | undefined) => {
const latest = last(sortBy(rows, 'created_time'))
return api('get-channel-messages', {
id: latest?.id,
channelId,
limit: 100,
}) as any
const [messages, setMessages] = usePersistentLocalState<
PrivateChatMessage[] | undefined
>(undefined, `private-messages-${channelId}-${limit}-v1`)
const [newestId, setNewestId] = usePersistentLocalState<number | undefined>(
undefined,
`private-message-newest-id-${channelId}`
)
const fetchMessages = async () => {
if (messages === undefined) {
const initialMessages = await api('get-channel-messages', {
channelId,
limit,
})
setMessages(initialMessages)
setNewestId(initialMessages[0]?.id)
} else if (newestId !== undefined) {
const newMessages = await api('get-channel-messages', {
channelId,
limit,
id: newestId,
})
setMessages((prevMessages) =>
orderBy(
uniqBy([...newMessages, ...(prevMessages ?? [])], (m) => m.id),
'createdTime',
'desc'
)
)
if (newMessages.length > 0 && newMessages[0].id !== newestId) {
setNewestId(newMessages[0].id)
}
}
}
useEffect(() => {
fetchMessages()
}, [channelId, limit, messages?.length, newestId])

useApiSubscription({
topics: ['private-user-messages/' + userId],
onBroadcast: () => {
fetchMessages()
},
})

const results = usePersistentApiPolling(
'private_user_messages',
allRowsQ,
newRowsOnlyQ,
`private-messages-${channelId}-${ms}ms-${initialLimit}limit-v1`,
{
ms,
deps: [channelId],
shouldUseLocalStorage: true,
}
)
return results
? orderBy(results.map(convertChatMessage), 'createdTime', 'desc')
: undefined
return messages
}

export const useHasUnseenPrivateMessage = (
userId: string,
channelId: number,
chats: ChatMessage[] | undefined
chats: PrivateChatMessage[] | undefined
) => {
const [lastSeenChatTime, setLastSeenChatTime] = usePersistentLocalState<
number | undefined
Expand Down
21 changes: 16 additions & 5 deletions web/pages/messages/[channelId].tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Page } from 'web/components/layout/page'
import { useRouter } from 'next/router'
import {
useRealtimePrivateMessagesPolling,
usePrivateMessages,
useSortedPrivateMessageMemberships,
} from 'web/hooks/use-private-messages'
import { Col } from 'web/components/layout/col'
Expand Down Expand Up @@ -50,6 +50,7 @@ import {
usePaginatedScrollingMessages,
} from 'web/lib/supabase/chat-messages'
import { PrivateMessageChannel } from 'common/supabase/private-messages'
import { ChatMessage } from 'common/chat-message'

export default function PrivateMessagesPage() {
const router = useRouter()
Expand Down Expand Up @@ -110,10 +111,10 @@ export const PrivateChat = (props: {
const isMobile = useIsMobile()

const totalMessagesToLoad = 100
const realtimeMessages = useRealtimePrivateMessagesPolling(
const realtimeMessages = usePrivateMessages(
channelId,
100,
totalMessagesToLoad
totalMessagesToLoad,
user.id
)

const [showUsers, setShowUsers] = useState(false)
Expand All @@ -137,7 +138,17 @@ export const PrivateChat = (props: {
const router = useRouter()

const { topVisibleRef, showMessages, messages, innerDiv, outerDiv } =
usePaginatedScrollingMessages(realtimeMessages, 200, user?.id)
usePaginatedScrollingMessages(
realtimeMessages?.map(
(m) =>
({
...m,
id: m.id.toString(),
} as ChatMessage)
),
200,
user?.id
)

const editor = useTextEditor({
key: `private-message-${channelId}-${user.id}`,
Expand Down
4 changes: 2 additions & 2 deletions web/pages/messages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { RelativeTimestamp } from 'web/components/relative-timestamp'
import { Title } from 'web/components/widgets/title'
import {
useHasUnseenPrivateMessage,
useRealtimePrivateMessagesPolling,
usePrivateMessages,
useSortedPrivateMessageMemberships,
} from 'web/hooks/use-private-messages'
import { useUser } from 'web/hooks/use-user'
Expand Down Expand Up @@ -75,7 +75,7 @@ export const MessageChannelRow = (props: {
const channelId = channel.channel_id
const otherUsers = useUsersInStore(otherUserIds, `${channelId}`, 100)

const messages = useRealtimePrivateMessagesPolling(channelId, 10000, 1)
const messages = usePrivateMessages(channelId, 1, currentUser.id)
const unseen = useHasUnseenPrivateMessage(currentUser.id, channelId, messages)
const chat = messages?.[0]
const numOthers = otherUsers?.length ?? 0
Expand Down

0 comments on commit 31f8b69

Please sign in to comment.