Skip to content

Commit

Permalink
feat: 实现消息加密
Browse files Browse the repository at this point in the history
  • Loading branch information
Sioncovy committed Jun 3, 2024
1 parent 5a6e052 commit 542f47c
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 121 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"await-to-js": "^3.0.0",
"axios": "^1.6.7",
"axios-hooks": "^5.0.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"dotenv": "^16.4.1",
"emoji-mart": "^5.5.2",
Expand All @@ -29,6 +30,7 @@
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"nanoid": "^5.0.7",
"node-forge": "^1.3.1",
"qiniu-js": "^3.4.2",
"qs": "^6.11.2",
"react": "^18.2.0",
Expand All @@ -46,8 +48,10 @@
},
"devDependencies": {
"@antfu/eslint-config": "^2.6.4",
"@types/crypto-js": "^4.2.2",
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.16",
"@types/node-forge": "^1.3.11",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
Expand Down
30 changes: 30 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/components/Chat/InputArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import TextButton from '@/components/TextButton'
import { socket, useAppStore, useThemeToken } from '@/hooks'
import { ChatMessageType, MessageSpecialType } from '@/typings'
import { createObjectURL } from '@/utils'
import { encryptMessage } from '@/utils/crypto'
import {
AudioOutlined,
ClockCircleOutlined,
Expand Down Expand Up @@ -233,8 +234,9 @@ function InputArea({ userId, friendId }: InputAreaProps) {
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()

socket.emit('sendChatMessage', {
content,
content: encryptMessage(content),
sender: userId,
receiver: friendId,
type: ChatMessageType.Text,
Expand Down
6 changes: 6 additions & 0 deletions src/components/Chat/Message/index.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@
display: none;
}
}

:global {
p {
line-break: anywhere;
}
}
2 changes: 1 addition & 1 deletion src/hooks/useSocket/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { REACT_APP_API } from '@/config'

export const socket = io(REACT_APP_API, {
transports: ['websocket'],
autoConnect: true,
autoConnect: true
})
163 changes: 150 additions & 13 deletions src/layouts/BasicLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { queryContactList, queryProfile } from '@/apis'
import Loading from '@/components/Loading'
import { AUTH_TOKEN_KEY } from '@/config'
import { socket, useAppStore, useThemeToken, useTime } from '@/hooks'
import { mailpenDatabase } from '@/storages'
import { ChatMessageType, Message, MessageSpecialType, Theme } from '@/typings'
import {
decryptMessage,
decryptRSAEncryptedAESKey,
generateRSAKeyPair
} from '@/utils/crypto'
import {
MessageOutlined,
SettingOutlined,
UserOutlined
} from '@ant-design/icons'
import { useLatest } from 'ahooks'
import type { GlobalToken } from 'antd'
import {
Avatar,
Expand All @@ -12,14 +24,9 @@ import {
Menu,
theme as themeAntd
} from 'antd'
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { socket, useAppStore, useThemeToken } from '@/hooks'
import { AUTH_TOKEN_KEY } from '@/config'
import Loading from '@/components/Loading'
import { queryContactList, queryProfile } from '@/apis'
import { Theme } from '@/typings'
import zh from 'antd/es/locale/zh_CN'
import { useEffect, useState } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'

function AddThemeToVars() {
const { token: realToken } = useThemeToken()
Expand All @@ -44,23 +51,141 @@ function AddThemeToVars() {
function BasicLayout(props: any) {
const navigate = useNavigate()
const { pathname } = useLocation()
const params = useParams()
const username = params.username as string
const [
theme,
userInfo,
setUserInfo,
setContactList,
primaryColor,
layoutColor
primaryColor
// layoutColor
] = useAppStore((state) => [
state.theme,
state.userInfo,
state.setUserInfo,
state.setContactList,
state.primaryColor,
state.layoutColor
state.primaryColor
// state.layoutColor
])
const [loading, setLoading] = useState(true)

const time = useTime()
const [contactMap] = useAppStore((state) => [state.contactMap])
const contactMapRef = useLatest(contactMap)

const readMessage = ({ id }: { id: string }) => {
mailpenDatabase.messages
.findOne({
selector: { _id: id }
})
.update({
$set: {
read: true
}
})
}

const updateMessage = (message: Message) => {
mailpenDatabase.messages
.findOne({ selector: { _id: message._id } })
.update({
$set: message
})
}

const receiveMessage = async (message: Message) => {
const sender = contactMapRef.current.get(message.sender)
const chat = await mailpenDatabase.chats
.findOne({ selector: { _id: sender?.username } })
.exec()
if (message.type === ChatMessageType.Text) {
message.content = decryptMessage(message.content)
}

if (!chat) {
sender &&
(await mailpenDatabase.chats.insert({
_id: sender.username,
name: sender.remark || sender.nickname || sender.username,
avatar: sender.avatar || '',
message,
count: 1,
createdAt: message.createdAt,
updatedAt: message.updatedAt,
pinned: false
}))
} else {
await chat.update({
$set: {
message,
count: chat.count + 1,
updatedAt: message.updatedAt
}
})
}
await mailpenDatabase.messages.insert(message)
}

const callbackChatMessage = async (message: Message) => {
const target = contactMapRef.current.get(message.receiver)

const chat = await mailpenDatabase.chats
.findOne({ selector: { _id: target?.username } })
.exec()
if (message.type === ChatMessageType.Text) {
message.content = decryptMessage(message.content)
}
if (!chat) return
await chat.update({
$set: {
message,
count: chat.count + 1,
updatedAt: message.updatedAt
}
})
await mailpenDatabase.messages.insert(message)
}

const withdrawMessage = async ({ id }: { id: string }) => {
await mailpenDatabase.messages.findOne({ selector: { _id: id } }).update({
$set: {
content: '消息已撤回',
type: ChatMessageType.Tip,
special: MessageSpecialType.Normal
}
})
await mailpenDatabase.chats
.findOne({ selector: { _id: username } })
.update({
$set: {
message: {
content: '消息已撤回',
type: ChatMessageType.Tip,
special: MessageSpecialType.Normal
}
}
})
}

useEffect(() => {
socket.on('receiveChatMessage', receiveMessage)
socket.on('callbackChatMessage', callbackChatMessage)

socket.on('onReadMessage', readMessage)
socket.on('onUpdateMessage', updateMessage)

socket.on('onWithdrawMessage', withdrawMessage)

return () => {
socket.off('receiveChatMessage', receiveMessage)
socket.off('callbackChatMessage', callbackChatMessage)
socket.off('onReadMessage', readMessage)
socket.off('onUpdateMessage', updateMessage)
socket.off('onWithdrawMessage', withdrawMessage)
}
}, [])

const selectedKey = pathname.split('/')[1]

const getContactList = () => {
Expand All @@ -78,10 +203,13 @@ function BasicLayout(props: any) {
}

queryProfile()
.then((res) => {
.then(async (res) => {
setUserInfo(res)
const { privateKeyPem, publicKeyPem } = generateRSAKeyPair()
localStorage.setItem('privateKey', privateKeyPem)
socket.emit('login', {
id: res._id
id: res._id,
key: publicKeyPem
})
})
.catch(() => {
Expand All @@ -92,6 +220,15 @@ function BasicLayout(props: any) {
})

getContactList()

socket.on('onAesKey', (data: { key: string }) => {
const key = decryptRSAEncryptedAESKey(data.key)
localStorage.setItem('aesKey', key)
})

return () => {
socket.off('onAesKey')
}
}, [])

if (loading) return <Loading height="100vh" />
Expand Down
Loading

0 comments on commit 542f47c

Please sign in to comment.