Skip to content

Commit

Permalink
add pusher to handle real time status
Browse files Browse the repository at this point in the history
  • Loading branch information
ngquyduc committed Jul 23, 2023
1 parent 13cae91 commit 0d4a83c
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 3 deletions.
6 changes: 5 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ NEXTAUTH_SECRET = "NEXTAUTH_SECRET"
GOOGLE_CLIENT_ID=409276087119-9hmgc0ad6augp7gkeaqmq2r1ru92afo0.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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

NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=dgp72bk0u
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=dgp72bk0u

NEXT_PUBLIC_PUSHER_APP_KEY = "78d72d7093049842333f"
PUSHER_APP_ID = "1639416"
PUSHER_SECRET = "4be3c4513301800c49dd"
11 changes: 11 additions & 0 deletions app/api/conversations/[conversationId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getCurrentUser from '@/app/actions/getCurrentUser'
import prisma from '@/app/libs/prismadb'
import { pusherServer } from '@/app/libs/pusher'
import { NextResponse } from 'next/server'

interface IParams {
Expand Down Expand Up @@ -39,6 +40,16 @@ export async function DELETE(
},
})

existingConversation.users.forEach((user) => {
if (user.email) {
pusherServer.trigger(
user.email,
'conversation:remove',
existingConversation
)
}
})

return NextResponse.json(deletedConversation)
} catch (error) {
console.log(error, 'ERROR DELETE CONVERSATION')
Expand Down
20 changes: 20 additions & 0 deletions app/api/conversations/[conversationId]/seen/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getCurrentUser from '@/app/actions/getCurrentUser'
import prisma from '@/app/libs/prismadb'
import { pusherServer } from '@/app/libs/pusher'
import { NextResponse } from 'next/server'

interface IParams {
Expand Down Expand Up @@ -53,6 +54,25 @@ export async function POST(request: Request, { params }: { params: IParams }) {
},
},
})

// Update all connections with new seen
await pusherServer.trigger(currentUser.email, 'conversation:update', {
id: conversationId,
messages: [updatedMessage],
})

// If user has already seen the message, no need to go further
if (lastMessage.seenIds.indexOf(currentUser.id) !== -1) {
return NextResponse.json(conversation)
}

// Update last message seen
await pusherServer.trigger(
conversationId!,
'message:update',
updatedMessage
)

return NextResponse.json(updatedMessage)
} catch (error: any) {
console.log(error, 'ERROR_MESSAGES_SEEN')
Expand Down
13 changes: 13 additions & 0 deletions app/api/conversations/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getCurrentUser from '@/app/actions/getCurrentUser'
import prisma from '@/app/libs/prismadb'
import { pusherServer } from '@/app/libs/pusher'
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
Expand Down Expand Up @@ -35,6 +36,12 @@ export async function POST(request: Request) {
},
})

newConversation.users.forEach((user) => {
if (user.email) {
pusherServer.trigger(user.email, 'conversation:new', newConversation)
}
})

return NextResponse.json(newConversation)
} else {
const existingConversations = await prisma.conversation.findMany({
Expand Down Expand Up @@ -78,6 +85,12 @@ export async function POST(request: Request) {
},
})

newConversation.users.forEach((user) => {
if (user.email) {
pusherServer.trigger(user.email, 'conversation:new', newConversation)
}
})

return NextResponse.json(newConversation)
}
} catch (error) {
Expand Down
13 changes: 13 additions & 0 deletions app/api/messages/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getCurrentUser from '@/app/actions/getCurrentUser'
import prisma from '@/app/libs/prismadb'
import { pusherServer } from '@/app/libs/pusher'
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
Expand Down Expand Up @@ -60,6 +61,18 @@ export async function POST(request: Request) {
},
})

await pusherServer.trigger(conversationId, 'messages:new', newMessage)

const lastMessage =
updatedConversation.messages[updatedConversation.messages.length - 1]

updatedConversation.users.map((user) => {
pusherServer.trigger(user.email!, 'conversation:update', {
id: conversationId,
messages: [lastMessage],
})
})

return NextResponse.json(newMessage)
} catch (error: any) {
console.log(error, 'ERROR_MESSAGES')
Expand Down
2 changes: 1 addition & 1 deletion app/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ConfirmModal: React.FC<ConfirmModalProps> = ({ isOpen, onClose }) => {
.delete(`/api/conversations/${conversationId}`)
.then(() => {
onClose()
router.push('/conversations`')
router.push('/conversations')
router.refresh()
})
.catch(() => toast.error('Something went wrong!'))
Expand Down
42 changes: 42 additions & 0 deletions app/conversations/[conversationId]/components/Body.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use client'

import useConversation from '@/app/hooks/useConversation'
import { pusherClient } from '@/app/libs/pusher'
import { FullMessageType } from '@/app/types'
import axios from 'axios'
import { find } from 'lodash'
import { useEffect, useRef, useState } from 'react'
import MessageBox from './MessageBox'

Expand All @@ -20,6 +22,46 @@ const Body: React.FC<BodyProps> = ({ initialMessages }) => {
axios.post(`/api/conversations/${conversationId}/seen`)
}, [conversationId])

useEffect(() => {
pusherClient.subscribe(conversationId)
bottomRef?.current?.scrollIntoView()

const messageHandler = (message: FullMessageType) => {
axios.post(`/api/conversations/${conversationId}/seen`)

setMessages((current) => {
if (find(current, { id: message.id })) {
return current
}

return [...current, message]
})

bottomRef?.current?.scrollIntoView()
}

const updateMessageHandler = (newMessage: FullMessageType) => {
setMessages((current) =>
current.map((currentMessage) => {
if (currentMessage.id === newMessage.id) {
return newMessage
}

return currentMessage
})
)
}

pusherClient.bind('messages:new', messageHandler)
pusherClient.bind('message:update', updateMessageHandler)

return () => {
pusherClient.unsubscribe(conversationId)
pusherClient.unbind('messages:new', messageHandler)
pusherClient.unbind('message:update', updateMessageHandler)
}
}, [conversationId])

return (
<div className="flex-1 overflow-y-auto">
{messages.map((message, i) => (
Expand Down
64 changes: 63 additions & 1 deletion app/conversations/components/ConversationList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use client'

import useConversation from '@/app/hooks/useConversation'
import { pusherClient } from '@/app/libs/pusher'
import { FullConversationType } from '@/app/types'
import { Conversation, User } from '@prisma/client'
import clsx from 'clsx'
import { find, uniq } from 'lodash'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { MdOutlineGroupAdd } from 'react-icons/md'
import ConversationBox from './ConversationBox'
import GroupChatModal from './GroupChatModal'
Expand All @@ -19,13 +22,72 @@ const ConversationList: React.FC<ConversationListProps> = ({
initialItems,
users,
}) => {
const session = useSession()
const [items, setItems] = useState(initialItems)
const [isModalOpen, setIsModalOpen] = useState(false)

const router = useRouter()

const { conversationId, isOpen } = useConversation()

const pusherKey = useMemo(() => {
return session.data?.user?.email
}, [session.data?.user?.email])

useEffect(() => {
if (!pusherKey) {
return
}

pusherClient.subscribe(pusherKey)

const newHandler = (conversation: FullConversationType) => {
setItems((current) => {
if (find(current, { id: conversation.id })) {
return current
}

return [conversation, ...current]
})
}

const updateHandler = (conversation: FullConversationType) => {
setItems((current) =>
current.map((currentConversation) => {
if (currentConversation.id === conversation.id) {
return {
...currentConversation,
messages: conversation.messages,
}
}

return currentConversation
})
)
}

const removeHandler = (conversation: FullConversationType) => {
setItems((current) => {
return [...current.filter((convo) => convo.id !== conversation.id)]
})

if (conversationId === conversation.id) {
router.push('/conversations')
}
}

pusherClient.bind('conversation:update', updateHandler)
pusherClient.bind('conversation:new', newHandler)
pusherClient.bind('conversation:remove', removeHandler)

return () => {
pusherClient.unsubscribe(pusherKey)
pusherClient.unbind('conversation:update', updateHandler)
pusherClient.unbind('conversation:new', newHandler)
pusherClient.unbind('conversation:remove', removeHandler)
}
}, [pusherKey, router])

return (
<>
<GroupChatModal
Expand Down
21 changes: 21 additions & 0 deletions app/libs/pusher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import PusherServer from 'pusher'
import PusherClient from 'pusher-js'

export const pusherServer = new PusherServer({
appId: process.env.PUSHER_APP_ID!,
key: process.env.NEXT_PUBLIC_PUSHER_APP_KEY!,
secret: process.env.PUSHER_SECRET!,
cluster: 'ap1',
useTLS: true,
})

export const pusherClient = new PusherClient(
process.env.NEXT_PUBLIC_PUSHER_APP_KEY!,
{
// channelAuthorization: {
// endpoint: '/api/pusher/auth',
// transport: 'ajax',
// },
cluster: 'ap1',
}
)
Loading

0 comments on commit 0d4a83c

Please sign in to comment.