Skip to content

Commit

Permalink
[JS] MessageDraftV2 + remove crypto usage (#144)
Browse files Browse the repository at this point in the history
* [JS] MessageDraftV2

* Remove crypto dependency by replacing JS UUIDv4 implementation

* Bump pubnub SDK version

* Bump Pubnub Swift version

* PubNub kotlin v0.9.2 release.

---------

Co-authored-by: PubNub Release Bot <[email protected]>
  • Loading branch information
wkal-pubnub and pubnub-release-bot authored Dec 12, 2024
1 parent f2848d9 commit ed7648f
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 87 deletions.
17 changes: 14 additions & 3 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: kmp-chat
version: 0.9.0
version: 0.9.2
schema: 1
scm: github.com/pubnub/kmp-chat
sdks:
Expand All @@ -21,8 +21,8 @@ sdks:
-
distribution-type: library
distribution-repository: maven
package-name: pubnub-chat-0.9.0
location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-chat/0.9.0/
package-name: pubnub-chat-0.9.2
location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-chat/0.9.2/
supported-platforms:
supported-operating-systems:
Android:
Expand Down Expand Up @@ -77,6 +77,17 @@ sdks:
license-url: https://github.com/pubnub/kotlin/blob/master/LICENSE
is-required: Required
changelog:
- date: 2024-12-12
version: v0.9.2
changes:
- type: feature
text: "Lock moderated messages from editing ."
- type: bug
text: "Wrong user suggestion source for message draft created on ThreadChannel."
- type: bug
text: "Wrong type of last user activity time stored on server (precision)."
- type: improvement
text: "Moderation events are now sent to a channel prefixed with `PUBNUB_INTERNAL_MODERATION.`."
- date: 2024-11-06
version: v0.9.0
changes:
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ let package = Package(
targets: [
.binaryTarget(
name: "PubNubChatRemoteBinaryPackage",
url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-v0.9.0/PubNubChat.xcframework.zip",
checksum: "e043957bd849c7243085368c0e64607cec6dc2e8db6c863f1a80c025d11f6497"
url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-v0.9.2/PubNubChat.xcframework.zip",
checksum: "ba90004881639c1ae7e05cf93d68554a77717f8f7c89515a1894cce03a557122"
)
]
)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ You will need the publish and subscribe keys to authenticate your app. Get your
<dependency>
<groupId>com.pubnub</groupId>
<artifactId>pubnub-chat</artifactId>
<version>0.9.0</version>
<version>0.9.2</version>
</dependency>
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.pubnub.gradle

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.tasks.testing.AbstractTestTask
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
Expand Down Expand Up @@ -38,9 +39,16 @@ class PubNubKotlinMultiplatformPlugin : Plugin<Project> {
}

pod("PubNubSwift") {
// val swiftPath = project.findProperty("SWIFT_PATH") as? String ?: "swift"
// source = path(rootProject.file(swiftPath))
version = "8.1.0"
val swiftPath = project.findProperty("SWIFT_PATH") as? String
if (swiftPath != null) {
source = path(rootProject.file(swiftPath))
} else {
version = project.rootProject
.extensions
.getByType(VersionCatalogsExtension::class.java)
.named("libs")
.findVersion("pubnub.swift").get().requiredVersion
}
moduleName = "PubNubSDK"
extraOpts += listOf("-compiler-option", "-fmodules")
}
Expand Down
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ kotlin {

compilerOptions {
target.set("es2015")
// moduleKind.set(JsModuleKind.MODULE_UMD)
}
binaries.library()
}
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SONATYPE_HOST=DEFAULT
SONATYPE_AUTOMATIC_RELEASE=false
GROUP=com.pubnub
POM_PACKAGING=jar
VERSION_NAME=0.9.1
VERSION_NAME=0.9.2

POM_NAME=PubNub Chat SDK
POM_DESCRIPTION=This SDK offers a set of handy methods to create your own feature-rich chat or add a chat to your existing application.
Expand All @@ -30,7 +30,7 @@ POM_DEVELOPER_NAME=PubNub
POM_DEVELOPER_URL=[email protected]

IOS_SIMULATOR_ID=iPhone 15 Pro
SWIFT_PATH=pubnub-kotlin/swift
#SWIFT_PATH=../swift

ENABLE_TARGET_JS=true
ENABLE_TARGET_IOS_OTHER=false
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ ktlint = "12.1.0"
dokka = "1.9.20"
kotlinx_serialization = "1.7.3"
kotlinx_coroutines = "1.9.0"
pubnub = "10.3.0"
pubnub = "10.3.1"
pubnub_swift = "8.2.2"

[libraries]
pubnub-kotlin-api = { module = "com.pubnub:pubnub-kotlin-api", version.ref = "pubnub" }
Expand Down
4 changes: 4 additions & 0 deletions js-chat/main.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export * from "../build/dist/js/productionLibrary/pubnub-chat.mjs"
export const INTERNAL_MODERATION_PREFIX = "PUBNUB_INTERNAL_MODERATION_"
export const MESSAGE_THREAD_ID_PREFIX = "PUBNUB_INTERNAL_THREAD";
export const INTERNAL_ADMIN_CHANNEL = "PUBNUB_INTERNAL_ADMIN_CHANNEL";
export const ERROR_LOGGER_KEY_PREFIX = "PUBNUB_INTERNAL_ERROR_LOGGER";

import PubNub from "pubnub"
export let CryptoModule = PubNub.CryptoModule
166 changes: 166 additions & 0 deletions js-chat/tests/message-draft-v2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Channel, Chat, MessageDraftV2, MixedTextTypedElement } from "../dist"
import {
createChatInstance,
createRandomChannel,
createRandomUser,
renderMessagePart,
sleep,
makeid
} from "./utils"
import { jest } from "@jest/globals"

describe("MessageDraft", function () {
jest.retryTimes(2)
let chat: Chat
let channel: Channel
let messageDraft: MessageDraftV2

beforeAll(async () => {
chat = await createChatInstance()
})

beforeEach(async () => {
channel = await createRandomChannel()
messageDraft = channel.createMessageDraftV2({ userSuggestionSource: "global" })
})

test("should mention 2 users", async () => {
const [user1, user2] = await Promise.all([createRandomUser(), createRandomUser()])

messageDraft.update("Hello @user1 and @user2")
messageDraft.addMention(6, 6, "mention", user1.id)
messageDraft.addMention(17, 6, "mention", user2.id)
const messagePreview = messageDraft.getMessagePreview()
expect(messagePreview.length).toBe(4)
expect(messagePreview[0].type).toBe("text")
expect(messagePreview[1].type).toBe("mention")
expect(messagePreview[2].type).toBe("text")
expect(messagePreview[3].type).toBe("mention")
expect(messageDraft.value).toBe(`Hello @user1 and @user2`)
expect(messagePreview.map(renderMessagePart).join("")).toBe(
`Hello @@user1 and @@user2`
)
await Promise.all([user1.delete({ soft: false }), user2.delete({ soft: false })])
})

test("should mention 2 - 3 users next to each other", async () => {
const [user1, user2, user3] = await Promise.all([
createRandomUser(),
createRandomUser(),
createRandomUser(),
])

let elements: MixedTextTypedElement[][] = []
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});

messageDraft.addChangeListener(async function(state) {
elements.push(state.messageElements)
if (elements.length == 3) {
resolve()
return
}
let mentions = await state.suggestedMentions
messageDraft.insertSuggestedMention(mentions[0], mentions[0].replaceWith)
})

messageDraft.update("Hello @Te @Tes @Test")
await promise

const messagePreview = messageDraft.getMessagePreview()
expect(messagePreview.length).toBe(4)
expect(messagePreview[0].type).toBe("text")
expect(messagePreview[1].type).toBe("mention")
expect(messagePreview[2].type).toBe("text")
expect(messagePreview[3].type).toBe("mention")
expect(messagePreview.map(renderMessagePart).join("")).toBe(
elements[2].map(renderMessagePart).join("")
)
await Promise.all([
user1.delete({ soft: false }),
user2.delete({ soft: false }),
user3.delete({ soft: false }),
])
})

test("should mix every type of message part", async () => {
const [channel1, channel2] = await Promise.all([createRandomChannel(makeid()), createRandomChannel(makeid())])
const [user1, user2, user4, user5] = await Promise.all([
createRandomUser(makeid()),
createRandomUser(makeid()),
createRandomUser(makeid()),
createRandomUser(makeid()),
])
messageDraft.update("Hello ")
messageDraft.addLinkedText({
text: "pubnub",
link: "https://pubnub.com",
positionInInput: messageDraft.value.length,
})
messageDraft.update("Hello pubnub at https://pubnub.com! Hello to ")
messageDraft.addLinkedText({
text: "google",
link: "https://google.com",
positionInInput: messageDraft.value.length,
})

let elements: MixedTextTypedElement[][] = []
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});

messageDraft.addChangeListener(async function(state) {
elements.push(state.messageElements)
let mentions = await state.suggestedMentions
if (mentions.length == 0) {
resolve()
return
}
messageDraft.insertSuggestedMention(mentions[0], mentions[0].replaceWith)
})


messageDraft.update(
`Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name.substring(0,8)}, #${channel2.name.substring(0,8)}, #blankchannel, @${user1.name.substring(0,8)}, @${user2.name.substring(0,8)}, and mentioning @blankuser3 @${user4.name.substring(0,8)} @${user5.name.substring(0,8)}`
)
await promise

const messagePreview = messageDraft.getMessagePreview()

expect(messagePreview.length).toBe(16)
expect(messagePreview[0].type).toBe("text")
expect(messagePreview[1].type).toBe("textLink")
expect(messagePreview[2].type).toBe("text")
expect(messagePreview[3].type).toBe("textLink")
expect(messagePreview[4].type).toBe("text")
expect(messagePreview[5].type).toBe("channelReference")
expect(messagePreview[6].type).toBe("text")
expect(messagePreview[7].type).toBe("channelReference")
expect(messagePreview[8].type).toBe("text")
expect(messagePreview[9].type).toBe("mention")
expect(messagePreview[10].type).toBe("text")
expect(messagePreview[11].type).toBe("mention")
expect(messagePreview[12].type).toBe("text")
expect(messagePreview[13].type).toBe("mention")
expect(messagePreview[14].type).toBe("text")
expect(messagePreview[15].type).toBe("mention")
expect(messagePreview.map(renderMessagePart).join("")).toBe(
`Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing #${channel1.name}, #${channel2.name}, #blankchannel, @${user1.name}, @${user2.name}, and mentioning @blankuser3 @${user4.name} @${user5.name}`
)
expect(messageDraft.value).toBe(
`Hello pubnub at https://pubnub.com! Hello to google at https://google.com. Referencing ${channel1.name}, ${channel2.name}, #blankchannel, ${user1.name}, ${user2.name}, and mentioning @blankuser3 ${user4.name} ${user5.name}`
)
await Promise.all([channel1.delete({ soft: false }), channel2.delete({ soft: false })])
await Promise.all([
user1.delete({ soft: false }),
user2.delete({ soft: false }),
user4.delete({ soft: false }),
])
})

})
4 changes: 2 additions & 2 deletions js-chat/tests/message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1037,11 +1037,11 @@ describe("Send message test", () => {

const firstThreadMessage = (await thread.getHistory()).messages[0]

const messageDraft = thread.createMessageDraft()
const messageDraft = thread.createMessageDraftV2()

messageDraft.addQuote(firstThreadMessage)

await messageDraft.onChange("This is a forwarded message.")
await messageDraft.update("This is a forwarded message.")
await messageDraft.send()

await sleep(500)
Expand Down
14 changes: 8 additions & 6 deletions js-chat/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,18 @@ export async function createChatInstance(
return chat
}

export function createRandomChannel() {
return chat.createChannel(`channel_${makeid()}`, {
name: "Test Channel",
export function createRandomChannel(prefix?: string) {
if (!prefix) prefix = ""
return chat.createChannel(`${prefix}channel_${makeid()}`, {
name: `${prefix}Test Channel`,
description: "This is a test channel",
})
}

export function createRandomUser() {
return chat.createUser(`user_${makeid()}`, {
name: "Test User",
export function createRandomUser(prefix?: string) {
if (!prefix) prefix = ""
return chat.createUser(`${prefix}user_${makeid()}`, {
name: `${prefix}Test User`,
})
}

Expand Down
13 changes: 13 additions & 0 deletions pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UuidTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.pubnub.kmp

import com.pubnub.chat.internal.generateRandomUuid
import kotlin.test.Test
import kotlin.test.assertTrue

class UuidTest {
@Test
fun generateUuid() {
val uuid = generateRandomUuid()
assertTrue { uuid.matches(Regex("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")) }
}
}
8 changes: 7 additions & 1 deletion pubnub-chat-impl/src/jsMain/kotlin/ChannelJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ open class ChannelJs internal constructor(internal val channel: Channel, interna

fun createMessageDraftV2(config: MessageDraftConfig?): MessageDraftV2Js {
return MessageDraftV2Js(
this.chatJs,
MessageDraftImpl(
this.channel,
config?.userSuggestionSource?.let {
Expand All @@ -209,7 +210,12 @@ open class ChannelJs internal constructor(internal val channel: Channel, interna
config?.userLimit ?: 10,
config?.channelLimit ?: 10
),
config
createJsObject<MessageDraftConfig> {
this.userSuggestionSource = config?.userSuggestionSource ?: "channel"
this.isTypingIndicatorTriggered = config?.isTypingIndicatorTriggered ?: (channel.type != ChannelType.PUBLIC)
this.userLimit = config?.userLimit ?: 10
this.channelLimit = config?.channelLimit ?: 10
}
)
}

Expand Down
Loading

0 comments on commit ed7648f

Please sign in to comment.