Skip to content

Commit

Permalink
feat(lib): add default ids to channels, create a new event and forwar…
Browse files Browse the repository at this point in the history
…d messages to the same channel
  • Loading branch information
piotr-suwala committed Oct 26, 2023
1 parent 4f86e9a commit 7ab016c
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 50 deletions.
61 changes: 52 additions & 9 deletions lib/src/entities/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ThreadChannel } from "./thread-channel"
import { MentionsUtils } from "../mentions-utils"
import { getErrorProxiedEntity, ErrorLogger } from "../error-logging"
import { cyrb53a } from "../hash"
import { uuidv4 } from "../uuidv4"

type ChatConfig = {
saveDebugLog: boolean
Expand Down Expand Up @@ -570,10 +571,16 @@ export class Chat {
channelId,
channelData,
}: {
channelId: string
channelId?: string
channelData: PubNub.ChannelMetadata<PubNub.ObjectCustom>
}) {
return this.createChannel(channelId, { name: channelId, ...channelData, type: "public" })
const finalChannelId = channelId || uuidv4()

return this.createChannel(finalChannelId, {
name: finalChannelId,
...channelData,
type: "public",
})
}

/**
Expand Down Expand Up @@ -617,11 +624,11 @@ export class Chat {
async forwardMessage(message: Message, channel: string) {
if (!channel) throw "Channel ID is required"
if (!message) throw "Message is required"
if (message.channelId === channel) throw "You cannot forward the message to the same channel"

const meta = {
...(message.meta || {}),
originalPublisher: message.userId,
originalChannelId: message.channelId,
}
this.publish({ message: message.content, channel, meta })
}
Expand Down Expand Up @@ -710,10 +717,12 @@ export class Chat {

async createDirectConversation({
user,
channelId,
channelData,
membershipData = {},
}: {
user: User
channelId?: string
channelData: PubNub.ChannelMetadata<PubNub.ObjectCustom>
membershipData?: Omit<
PubNub.SetMembershipsParameters<PubNub.ObjectCustom>,
Expand All @@ -729,11 +738,15 @@ export class Chat {

const sortedUsers = [this.user.id, user.id].sort()

const channelId = `direct.${cyrb53a(`${sortedUsers[0]}&${sortedUsers[1]}`)}`
const finalChannelId = channelId || `direct.${cyrb53a(`${sortedUsers[0]}&${sortedUsers[1]}`)}`

const channel =
(await this.getChannel(channelId)) ||
(await this.createChannel(channelId, { name: channelId, ...channelData, type: "direct" }))
(await this.getChannel(finalChannelId)) ||
(await this.createChannel(finalChannelId, {
name: finalChannelId,
...channelData,
type: "direct",
}))

const { custom, ...rest } = membershipData
const hostMembershipPromise = this.sdk.objects.setMemberships({
Expand All @@ -753,6 +766,16 @@ export class Chat {
channel.invite(user),
])

await this.emitEvent({
channel: user.id,
type: "invite",
method: "publish",
payload: {
channelType: "direct",
channelId: channel.id,
},
})

return {
channel,
hostMembership: Membership.fromMembershipDTO(
Expand All @@ -774,7 +797,7 @@ export class Chat {
membershipData = {},
}: {
users: User[]
channelId: string
channelId?: string
channelData: PubNub.ChannelMetadata<PubNub.ObjectCustom>
membershipData?: Omit<
PubNub.SetMembershipsParameters<PubNub.ObjectCustom>,
Expand All @@ -783,10 +806,16 @@ export class Chat {
custom?: PubNub.ObjectCustom
}
}) {
const finalChannelId = channelId || uuidv4()

try {
const channel =
(await this.getChannel(channelId)) ||
(await this.createChannel(channelId, { name: channelId, ...channelData, type: "group" }))
(await this.getChannel(finalChannelId)) ||
(await this.createChannel(finalChannelId, {
name: finalChannelId,
...channelData,
type: "group",
}))
const { custom, ...rest } = membershipData
const hostMembershipPromise = this.sdk.objects.setMemberships({
...rest,
Expand All @@ -805,6 +834,20 @@ export class Chat {
channel.inviteMultiple(users),
])

await Promise.all(
users.map(async (u) => {
await this.emitEvent({
channel: u.id,
method: "publish",
type: "invite",
payload: {
channelType: "group",
channelId: channel.id,
},
})
})
)

return {
channel,
hostMembership: Membership.fromMembershipDTO(
Expand Down
4 changes: 4 additions & 0 deletions lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export type EventContent = {
messageTimetoken: string
channel: string
}
invite: {
channelType: "direct" | "group"
channelId: string
}
custom: any
}

Expand Down
56 changes: 56 additions & 0 deletions lib/src/uuidv4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const byteToHex: string[] = []

for (let i = 0; i < 256; i++) {
byteToHex[i] = (i + 0x100).toString(16).substr(1)
}

const unparse = (buf: Array<number>, offset?: number) => {
let i = offset || 0
const bth = byteToHex

return (
bth[buf[i++]] +
bth[buf[i++]] +
bth[buf[i++]] +
bth[buf[i++]] +
"-" +
bth[buf[i++]] +
bth[buf[i++]] +
"-" +
bth[buf[i++]] +
bth[buf[i++]] +
"-" +
bth[buf[i++]] +
bth[buf[i++]] +
"-" +
bth[buf[i++]] +
bth[buf[i++]] +
bth[buf[i++]] +
bth[buf[i++]] +
bth[buf[i++]] +
bth[buf[i++]]
)
}

const min = 0
const max = 256
const RANDOM_LENGTH = 16

export const rng = () => {
const result = new Array<number>(RANDOM_LENGTH)

for (let j = 0; j < RANDOM_LENGTH; j++) {
result[j] = 0xff & (Math.random() * (max - min) + min)
}

return result
}

export const uuidv4 = () => {
const rnds: number[] = rng()

rnds[6] = (rnds[6] & 0x0f) | 0x40
rnds[8] = (rnds[8] & 0x3f) | 0x80

return unparse(rnds)
}
105 changes: 105 additions & 0 deletions lib/tests/channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,86 @@ describe("Channel test", () => {
await channel.leave()
})

test("should create a direct, group and public chats with a predefined ID", async () => {
const someFakeDirectId = "someFakeDirectId"
const someFakeGroupId = "someFakeGroupId"
const someFakePublicId = "someFakePublicId"

const existingChannels = await Promise.all(
[someFakeDirectId, someFakeGroupId, someFakePublicId].map((id) => chat.getChannel(id))
)

for (const existingChannel of existingChannels) {
if (existingChannel) {
await existingChannel.delete({ soft: false })
}
}

const user = await createRandomUser()

const newChannels = await Promise.all([
chat.createDirectConversation({
user,
channelId: someFakeDirectId,
channelData: {},
}),
chat.createGroupConversation({
users: [user],
channelId: someFakeGroupId,
channelData: {},
}),
chat.createPublicConversation({
channelId: someFakePublicId,
channelData: {},
}),
])

expect(newChannels[0].channel.id).toBe(someFakeDirectId)
expect(newChannels[1].channel.id).toBe(someFakeGroupId)
expect(newChannels[2].id).toBe(someFakePublicId)

await newChannels[0].channel.delete({ soft: false })
await newChannels[1].channel.delete({ soft: false })
await newChannels[2].delete({ soft: false })
})

test("should create a direct, group and public chats with default IDs", async () => {
const user = await createRandomUser()

const newChannels = await Promise.all([
chat.createDirectConversation({
user,
channelData: {},
}),
chat.createGroupConversation({
users: [user],
channelData: {},
}),
chat.createPublicConversation({
channelData: {},
}),
])

expect(newChannels[0].channel.id.startsWith("direct.")).toBeTruthy()
expect(newChannels[1].channel.id).toBeDefined()
expect(newChannels[2].id).toBeDefined()

await newChannels[0].channel.delete({ soft: false })
await newChannels[1].channel.delete({ soft: false })
await newChannels[2].delete({ soft: false })
})

test("should create direct conversation and send message", async () => {
const user = await createRandomUser()
expect(user).toBeDefined()
const inviteCallback = jest.fn()

const removeInvitationListener = chat.listenForEvents({
channel: user.id,
type: "invite",
method: "publish",
callback: inviteCallback,
})

const directConversation = await chat.createDirectConversation({
user,
Expand All @@ -151,13 +228,31 @@ describe("Channel test", () => {
(message: Message) => message.content.text === messageText
)
expect(messageInHistory).toBeTruthy()
expect(inviteCallback).toHaveBeenCalledTimes(1)
expect(inviteCallback).toHaveBeenCalledWith(
expect.objectContaining({
payload: {
channelType: "direct",
channelId: directConversation.channel.id,
},
})
)
await user.delete()
removeInvitationListener()
})

test("should create group conversation", async () => {
const user1 = await createRandomUser()
const user2 = await createRandomUser()
const user3 = await createRandomUser()
const inviteCallback = jest.fn()

const removeInvitationListener = chat.listenForEvents({
channel: user1.id,
type: "invite",
method: "publish",
callback: inviteCallback,
})

const channelId = "group_channel_1234"
const channelData = {
Expand Down Expand Up @@ -190,11 +285,21 @@ describe("Channel test", () => {
expect(channel.description).toEqual("This is a test group channel.")
expect(channel.custom.groupInfo).toEqual("Additional group information")
expect(inviteesMemberships.length).toEqual(3)
expect(inviteCallback).toHaveBeenCalledTimes(1)
expect(inviteCallback).toHaveBeenCalledWith(
expect.objectContaining({
payload: {
channelType: "group",
channelId: result.channel.id,
},
})
)

await user1.delete()
await user2.delete()
await user3.delete()
await channel.delete()
removeInvitationListener()
})

test("should create a thread", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,6 @@ export function NewChatScreen({ navigation }: StackScreenProps<HomeStackParamLis
user,
channelData: { name: `1:1 with ${user.name}` },
})
await chat.emitEvent({
channel: user.id,
method: "publish",
payload: {
action: "DIRECT_CONVERSATION_STARTED",
channelId: channel.id,
},
})
setCurrentChannel(channel)
setLoading(false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,6 @@ export function NewGroupScreen({ navigation }: StackScreenProps<HomeStackParamLi
channelId: nanoid(),
channelData: { name: groupName },
})
await Promise.all(
selectedUsers.map(async (u) => {
await chat.emitEvent({
channel: u.id,
method: "publish",
payload: {
action: "GROUP_CONVERSATION_STARTED",
channelId: channel.id,
},
})
})
)
setCurrentChannel(channel)
setLoading(false)
}
Expand Down
Loading

0 comments on commit 7ab016c

Please sign in to comment.