Skip to content

Commit

Permalink
feat(lib): lock messages from being edited
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-suwala committed Nov 22, 2024
1 parent 93ad9f2 commit 6136ee1
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 17 deletions.
4 changes: 4 additions & 0 deletions lib/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ export const MESSAGE_THREAD_ID_PREFIX = "PUBNUB_INTERNAL_THREAD"
export const INTERNAL_MODERATION_PREFIX = "PUBNUB_INTERNAL_MODERATION_"
export const INTERNAL_ADMIN_CHANNEL = "PUBNUB_INTERNAL_ADMIN_CHANNEL"
export const ERROR_LOGGER_KEY_PREFIX = "PUBNUB_INTERNAL_ERROR_LOGGER"
export const INTERNAL_MODERATOR_DATA = {
id: "PUBNUB_INTERNAL_MODERATOR",
type: "internal",
}
4 changes: 4 additions & 0 deletions lib/src/entities/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ export class Message {
async editText(newText: string) {
const type = this.chat.editMessageActionName
try {
if (this.meta?.PUBNUB_INTERNAL_AUTOMODERATED && !this.chat.currentUser.isInternalModerator) {
throw "The automoderated message can no longer be edited"
}

const { data } = await this.chat.sdk.addMessageAction({
channel: this.channelId,
messageTimetoken: this.timetoken,
Expand Down
11 changes: 10 additions & 1 deletion lib/src/entities/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Chat } from "./chat"
import { DeleteParameters, OptionalAllBut } from "../types"
import { Channel } from "./channel"
import { Membership } from "./membership"
import { INTERNAL_ADMIN_CHANNEL, INTERNAL_MODERATION_PREFIX } from "../constants"
import {
INTERNAL_ADMIN_CHANNEL,
INTERNAL_MODERATION_PREFIX,
INTERNAL_MODERATOR_DATA,
} from "../constants"
import { getErrorProxiedEntity } from "../error-logging"

export type UserFields = Pick<
Expand Down Expand Up @@ -56,6 +60,11 @@ export class User {
return getErrorProxiedEntity(new User(chat, data), chat.errorLogger)
}

/** @internal */
get isInternalModerator() {
return this.id === INTERNAL_MODERATOR_DATA.id && this.type === INTERNAL_MODERATOR_DATA.type
}

get active() {
return !!(
this.lastActiveTimestamp &&
Expand Down
5 changes: 3 additions & 2 deletions samples/react-native-group-chat/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ function MainRoutesNavigator({ route }: StackScreenProps<RootStackParamList, "ma
const { authKey } = await getAuthKey(name || "test-user")

const chat = await Chat.init({
publishKey: process.env.EXPO_PUBLIC_PUBNUB_PUB_KEY || "demo",
subscribeKey: process.env.EXPO_PUBLIC_PUBNUB_SUB_KEY || "demo",
publishKey: "pub-c-37da8074-b084-4e24-8550-4b5294c437c1", // process.env.EXPO_PUBLIC_PUBNUB_PUB_KEY || "demo",
subscribeKey: "sub-c-3c30ba92-6909-44da-a2d0-65bb8515b0b8", // process.env.EXPO_PUBLIC_PUBNUB_SUB_KEY || "demo",
origin: "ingress-http.pdx1.aws.int.ps.pn",
userId: name || "test-user",
typingTimeout: 2000,
storeUserActivityTimestamps: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type UseActionsMenuParams = {
onQuote: (message: Message) => void
removeThreadReply?: boolean
onPinMessage: (message: Message | ThreadMessage) => void
onEditMessage: (message: Message | ThreadMessage) => void
onToggleEmoji: (message: Message) => void
onDeleteMessage: (message: Message) => void
}
Expand All @@ -29,6 +30,7 @@ export function useActionsMenu({
onPinMessage,
onToggleEmoji,
onDeleteMessage,
onEditMessage,
}: UseActionsMenuParams) {
const bottomSheetModalRef = useRef<BottomSheetModal>(null)
const navigation = useNavigation<HomeStackNavigation>()
Expand All @@ -38,7 +40,7 @@ export function useActionsMenu({
)

// variables
const snapPoints = useMemo(() => ["25%", "50%"], [])
const snapPoints = useMemo(() => ["25%", "50%", "75%"], [])

// callbacks
const handlePresentModalPress = useCallback(({ message }: { message: EnhancedIMessage }) => {
Expand All @@ -63,7 +65,7 @@ export function useActionsMenu({
const ActionsMenuComponent = () => (
<BottomSheetModal
ref={bottomSheetModalRef}
index={1}
index={2}
snapPoints={snapPoints}
onChange={handleSheetChanges}
style={styles.container}
Expand Down Expand Up @@ -161,6 +163,26 @@ export function useActionsMenu({
Pin message
</Button>
<Gap value={16} />
{currentlyFocusedMessage?.originalPnMessage.userId === chat?.currentUser.id && (
<>
<Button
size="md"
align="left"
icon="edit"
variant="outlined"
onPress={() => {
if (currentlyFocusedMessage) {
onEditMessage(currentlyFocusedMessage.originalPnMessage)
setCurrentlyFocusedMessage(null)
bottomSheetModalRef.current?.dismiss()
}
}}
>
Edit message
</Button>
<Gap value={16} />
</>
)}
</>
) : null}
{currentlyFocusedMessage?.originalPnMessage.userId === chat?.currentUser.id ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { useNavigation } from "@react-navigation/native"

type MessageTextProps = {
onGoToMessage: (message: Message) => void
isBeingEdited?: boolean
messageProps: Bubble<EnhancedIMessage>["props"]
}

export function MessageText({ onGoToMessage, messageProps }: MessageTextProps) {
export function MessageText({ onGoToMessage, messageProps, isBeingEdited }: MessageTextProps) {
const { chat, setCurrentChannel } = useContext(ChatContext)
const navigation = useNavigation()
const [imageSrc, setImageSrc] = useState("")
Expand Down Expand Up @@ -146,7 +147,7 @@ export function MessageText({ onGoToMessage, messageProps }: MessageTextProps) {

if (messageElements) {
return (
<View>
<View style={isBeingEdited ? { borderWidth: 1, borderColor: "red" } : undefined}>
{messageProps.currentMessage?.originalPnMessage.quotedMessage ? (
<Quote
message={messageProps.currentMessage?.originalPnMessage.quotedMessage}
Expand Down
4 changes: 4 additions & 0 deletions samples/react-native-group-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@react-navigation/stack": "^6.3.17",
"expo": "^51.0.38",
"expo-clipboard": "~6.0.3",
"expo-crypto": "^14.0.1",
"expo-font": "~12.0.10",
"expo-status-bar": "~1.12.1",
"nanoid": "4.0.2",
Expand Down Expand Up @@ -53,5 +54,8 @@
"react-native-svg-transformer": "^1.1.0",
"typescript": "~5.3.3"
},
"browser": {
"crypto": false
},
"private": true
}
84 changes: 75 additions & 9 deletions samples/react-native-group-chat/screens/ordinary/chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "react-native"
import { GiftedChat, Bubble } from "react-native-gifted-chat"
import { StackScreenProps } from "@react-navigation/stack"
import { User, MessageDraft, Message, Channel } from "@pubnub/chat"
import { User, MessageDraft, Message, Channel, ThreadMessage } from "@pubnub/chat"

import { EnhancedIMessage, mapPNMessageToGChatMessage } from "../../../utils"
import { ChatContext } from "../../../context"
Expand Down Expand Up @@ -107,11 +107,37 @@ export function ChatScreen({}: StackScreenProps<HomeStackParamList, "Chat">) {
}
}, [])

const handleEditMessage = useCallback(
async (message: Message) => {
if (!currentChannel) {
return
}
const newMessageDraft = currentChannel.createMessageDraft({
userSuggestionSource: "global",
isTypingIndicatorTriggered: currentChannel.type !== "public",
})
newMessageDraft.value = message.text
setText(newMessageDraft.value)
setGiftedChatMappedMessages((curr) =>
curr.map((m) => {
return {
...m,
isBeingEdited: m.originalPnMessage.timetoken === message.timetoken,
}
})
)

setMessageDraft(newMessageDraft)
},
[currentChannel]
)

const { ActionsMenuComponent, handlePresentModalPress } = useActionsMenu({
onQuote: handleQuote,
onPinMessage: handlePin,
onToggleEmoji: handleEmoji,
onDeleteMessage: handleDeleteMessage,
onEditMessage: handleEditMessage,
})

useEffect(() => {
Expand Down Expand Up @@ -245,6 +271,17 @@ export function ChatScreen({}: StackScreenProps<HomeStackParamList, "Chat">) {
}
}, [currentChannel, currentChannelMembership])

function resetMessageBeingEdited() {
setGiftedChatMappedMessages((curr) =>
curr.map((m) => {
return {
...m,
isBeingEdited: false,
}
})
)
}

const resetInput = () => {
if (!messageDraft) {
return
Expand All @@ -254,6 +291,11 @@ export function ChatScreen({}: StackScreenProps<HomeStackParamList, "Chat">) {
messageDraft.files = undefined
setText("")
setImage("")
resetMessageBeingEdited()
}

function getMessageBeingEdited() {
return giftedChatMappedMessages.find((m) => m.isBeingEdited)?.originalPnMessage
}

const onSend = async () => {
Expand All @@ -262,17 +304,29 @@ export function ChatScreen({}: StackScreenProps<HomeStackParamList, "Chat">) {
}

try {
await messageDraft.send()
const messageBeingEdited = getMessageBeingEdited()
if (messageBeingEdited) {
await messageBeingEdited.editText(messageDraft.value)
} else {
await messageDraft.send()
}
} catch (error) {
let alertFn = (_: string) => null
if (Platform.OS === "web") {
alertFn = alert
} else {
alertFn = Alert.alert
}

if (typeof error === "string") {
alertFn(error)
resetMessageBeingEdited()
}
const e = error as { status: { errorData: { status: number } } }
if (e?.status?.errorData?.status !== 403) {
return
}
if (Platform.OS === "web") {
alert(`You cannot send messages to this channel: ${currentChannel?.id}`)
} else {
Alert.alert("You cannot send messages to this channel:", currentChannel?.id)
}
alertFn(`You cannot send messages to this channel: ${currentChannel?.id}`)
}
resetInput()
}
Expand Down Expand Up @@ -311,13 +365,25 @@ export function ChatScreen({}: StackScreenProps<HomeStackParamList, "Chat">) {
}

const renderBubble = (props: Bubble<EnhancedIMessage>["props"]) => {
const isBeingEditedStyles = props.currentMessage.isBeingEdited
? {
borderWidth: 1,
borderColor: colors.teal700,
}
: {}

return (
<View>
<Bubble
{...props}
wrapperStyle={{
left: { padding: 12, backgroundColor: colors.neutral50 },
right: { marginLeft: 0, padding: 12, backgroundColor: colors.teal100 },
right: {
marginLeft: 0,
padding: 12,
backgroundColor: colors.teal100,
...isBeingEditedStyles,
},
}}
/>
{props.currentMessage?.originalPnMessage.hasThread ? (
Expand Down Expand Up @@ -384,7 +450,7 @@ export function ChatScreen({}: StackScreenProps<HomeStackParamList, "Chat">) {
<SafeAreaView style={styles.content}>
<GiftedChat
messages={giftedChatMappedMessages}
onSend={(messages) => onSend(messages)}
onSend={onSend}
onInputTextChanged={handleInputChange}
renderMessageText={renderMessageText}
renderFooter={renderFooter}
Expand Down
5 changes: 5 additions & 0 deletions samples/react-native-group-chat/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ module.exports = async function (env, argv) {
}
})

config.resolve.fallback = {
...config.resolve.fallback,
crypto: require.resolve("expo-crypto"),
}

// Finally return the new config for the CLI to use.
return config
}
14 changes: 13 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6494,7 +6494,7 @@ __metadata:
languageName: node
linkType: hard

"base64-js@npm:^1.0.2, base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
"base64-js@npm:^1.0.2, base64-js@npm:^1.2.3, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf
Expand Down Expand Up @@ -9334,6 +9334,17 @@ __metadata:
languageName: node
linkType: hard

"expo-crypto@npm:^14.0.1":
version: 14.0.1
resolution: "expo-crypto@npm:14.0.1"
dependencies:
base64-js: "npm:^1.3.0"
peerDependencies:
expo: "*"
checksum: 10c0/3a240c83c4e4282d6c23267efdb25276fba73c9848c98eca019e8b48326941662eeb131babab04fec5913cae02a62c7e5b8f161d1657fa2e59048fa429253a5a
languageName: node
linkType: hard

"expo-file-system@npm:~17.0.1":
version: 17.0.1
resolution: "expo-file-system@npm:17.0.1"
Expand Down Expand Up @@ -15518,6 +15529,7 @@ __metadata:
babel-plugin-transform-inline-environment-variables: "npm:^0.4.4"
expo: "npm:^51.0.38"
expo-clipboard: "npm:~6.0.3"
expo-crypto: "npm:^14.0.1"
expo-font: "npm:~12.0.10"
expo-status-bar: "npm:~1.12.1"
nanoid: "npm:4.0.2"
Expand Down

0 comments on commit 6136ee1

Please sign in to comment.