Skip to content
Draft
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ POM_DEVELOPER_NAME=PubNub
[email protected]

IOS_SIMULATOR_ID=iPhone 15 Pro
#SWIFT_PATH=../swift
#SWIFT_PATH=../swift # swift project sourcecode is in the same folder as kotlin source

ENABLE_TARGET_JS=true
ENABLE_TARGET_IOS_OTHER=false
Expand Down
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jetbrains-annotations = { module = "org.jetbrains:annotations", version = "24.1.
kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version = "0.24.0" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx_datetime"}
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx_coroutines"}
touchlab-kermit = { module = "co.touchlab:kermit", version = "2.0.4" }

# tests
wiremock = { module = "com.github.tomakehurst:wiremock", version = "2.27.2" }
Expand Down Expand Up @@ -64,4 +65,5 @@ benmanes-versions = { id = "com.github.ben-manes.versions", version = "0.42.0" }
vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktech" }
lombok = { id = "io.freefair.lombok", version = "8.6" }
gradle-nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus" }
codingfeline-buildkonfig = { id = "com.codingfeline.buildkonfig", version = "0.15.1" }
codingfeline-buildkonfig = { id = "com.codingfeline.buildkonfig", version = "0.15.1" }
mokkery = { id = "dev.mokkery", version = "2.6.0" }
10 changes: 10 additions & 0 deletions pubnub-matchmaking-kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
alias(libs.plugins.benmanes.versions)
id("pubnub.kotlin-library")
id("pubnub.dokka")
}

dependencies {
api(project(":pubnub-matchmaking-kotlin:pubnub-matchmaking-kotlin-api"))
implementation(project(":pubnub-matchmaking-kotlin:pubnub-matchmaking-kotlin-impl"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import com.pubnub.gradle.enableAnyIosTarget // why ENABLE_TARGET_IOS_OTHER=false
import com.pubnub.gradle.enableJsTarget

plugins {
alias(libs.plugins.benmanes.versions)
id("pubnub.shared")
id("pubnub.dokka")
id("pubnub.multiplatform") // adds plugin to enables KMP
id("pubnub.ios-simulator-test") // todo what is this -> do odpalania testów na symulatorze ios
}

kotlin {
sourceSets {
val commonMain by getting {
dependencies {
api(project(":pubnub-kotlin:pubnub-kotlin-api"))
// todo do sprawdzania na projekcie z niższym kotlinem wymuszając niższa wersję odpowiednią komenda
implementation(libs.kotlinx.atomicfu) // todo in kotlin 2.1.2 this will be in standard library
}
}
val jvmMain by getting {
dependencies {
api(libs.retrofit2)
api(libs.okhttp)
api(libs.okhttp.logging)
api(libs.gson)
implementation(libs.slf4j)
}
}
if (enableAnyIosTarget) {
val appleMain by getting {
dependencies {
}
}
}

if (enableJsTarget) {
val jsMain by getting {
dependencies {
}
}
}

val commonTest by getting {
dependencies {
// implementation(project(":pubnub-kotlin:pubnub-kotlin-test")) // todo not needed for now
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.pubnub.matchmaking

import com.pubnub.api.PubNub
import com.pubnub.api.models.consumer.objects.PNKey
import com.pubnub.api.models.consumer.objects.PNPage
import com.pubnub.api.models.consumer.objects.PNSortKey
import com.pubnub.kmp.CustomObject
import com.pubnub.kmp.PNFuture
import com.pubnub.matchmaking.entities.FindMatchResult
import com.pubnub.matchmaking.entities.GetUsersResponse
import com.pubnub.matchmaking.entities.MatchmakingStatus

interface Matchmaking {
val pubNub: PubNub

fun createUser(
id: String,
name: String? = null,
externalId: String? = null,
profileUrl: String? = null,
email: String? = null,
custom: Any? = null,
status: String? = null,
type: String? = null,
): PNFuture<User>

fun getUser(userId: String): PNFuture<User>

/**
* Returns a paginated list of all users and their details
*
* @param filter Expression used to filter the results. Returns only these users whose properties satisfy the
* given expression are returned. The filtering language is defined in [documentation](https://www.pubnub.com/docs/general/metadata/filtering).
* @param sort A collection to specify the sort order. Available options are id, name, and updated. Use asc or desc
* @param limit Number of objects to return in response. The default (and maximum) value is 100.
* @param page Object used for pagination to define which previous or next result page you want to fetch.
*
* @return [PNFuture] containing a set of users with pagination information (next, prev, total).
*/
fun getUsers(
filter: String? = null,
sort: Collection<PNSortKey<PNKey>> = listOf(),
limit: Int? = null,
page: PNPage? = null,
): GetUsersResponse

fun updateUser(
id: String,
// TODO change nulls to Optionals when there is support. In Kotlin SDK there should be possibility to handle PatchValue
name: String? = null,
externalId: String? = null,
profileUrl: String? = null,
email: String? = null,
custom: CustomObject? = null,
status: String? = null,
type: String? = null,
): PNFuture<User>

fun deleteUser(id: String, soft: Boolean = false): PNFuture<User?>

fun findMatch(userId: String): PNFuture<String>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems overly complicated, couldn't the whole API be just one method -

@Throws(MatchMakingException::class)
fun findMatch(forUserId: String): PNFuture<MatchResult>

where MatchResult contains the set of users that were matched or the ID of the game or some other means of starting the game or confirming the match?

in case of error you would get an exception with details of the error, in case of success you get the result

why would I want to observe statuses like IN_QUEUE, RE_ADDED_TO_QUEUE etc, it's not useful?


fun findMatch(userId: String, callback: ((MatchmakingStatus) -> Unit)?): PNFuture<FindMatchResult>

fun getMatchStatus(userId: String): PNFuture<MatchmakingStatus>

fun cancelMatchmaking(userId: String): PNFuture<Unit>

// todo implement
fun addMissingUserToMatch(userId: String): PNFuture<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.pubnub.matchmaking

import com.pubnub.kmp.CustomObject
import com.pubnub.kmp.PNFuture

// todo add kdoc
interface User {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's concerning that we're copying and creating yet another implementation of User, this time in com.pubnub.matchmaking. If someone uses Chat SDK are they going to have two incompatible User classes? Should this SDK be an additon to Chat SDK? Or maybe User and Channel should be in some base module, used by both Chat and Matchmaking?

this could use some more thought to find the best solution

val matchmaking: Matchmaking
val id: String
val name: String?
val externalId: String?
val profileUrl: String?
val email: String?
val custom: Map<String, Any?>?
val status: String?
val type: String?
val updated: String?
val eTag: String?

fun update(
name: String? = null,
externalId: String? = null,
profileUrl: String? = null,
email: String? = null,
custom: CustomObject? = null,
status: String? = null,
type: String? = null,
): PNFuture<User>

fun update(
updateAction: UpdatableValues.(
user: User
) -> Unit
): PNFuture<User>

fun delete(soft: Boolean = false): PNFuture<User?>

class UpdatableValues(
/**
* The new value for [User.name].
*/
var name: String? = null,
/**
* The new value for [User.externalId].
*/
var externalId: String? = null,
/**
* The new value for [User.profileUrl].
*/
var profileUrl: String? = null,
/**
* The new value for [User.email].
*/
var email: String? = null,
/**
* The new value for [User.custom].
*/
var custom: CustomObject? = null,
/**
* The new value for [User.status].
*/
var status: String? = null,
/**
* The new value for [User.type].
*/
var type: String? = null,
)

companion object
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pubnub.matchmaking.entities

class FindMatchResult(
val result: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String? what's inside?

val disconnect: AutoCloseable?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.pubnub.matchmaking.entities

import com.pubnub.api.models.consumer.objects.PNPage
import com.pubnub.matchmaking.User

class GetUsersResponse(
val users: List<User>,
val next: PNPage.PNNext?,
val prev: PNPage.PNPrev?,
val total: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.pubnub.matchmaking.entities

interface MatchmakingCallback {
fun onMatchFound(match: MatchmakingGroup?)

fun onMatchmakingFailed(reason: String?)

fun onStatusChange(status: MatchmakingStatus?)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.pubnub.matchmaking.entities

import com.pubnub.matchmaking.User

class MatchmakingGroup(val users: Set<User>) // todo currently we returns Set<User>. What about Set<userid> ?
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.pubnub.matchmaking.entities

enum class MatchmakingStatus {
IN_QUEUE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what can a user do with all those statuses?

for example, why do we have an UNKNOWN status? we control this code, so shouldn't all of them be known?
what's the difference between IN_QUEUE and RE_ADDED_TO_QUEUE (from user's perspective)?

can we figure out the minimum list of statuses to make this work?

MATCHMAKING_STARTED,
RE_ADDED_TO_QUEUE,
MATCH_FOUND,
CANCELLED, // todo how to cancel matchmaking?
FAILED,
UNKNOWN,

INITIALLY_MATCHED, // todo do we need it,
WAITING_FOR_CONFIRMATION; // todo do we need it,

companion object {
fun fromString(value: String): MatchmakingStatus {
return values().find { it.name.equals(value, ignoreCase = true) } ?: UNKNOWN
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)

import com.pubnub.gradle.enableAnyIosTarget
import com.pubnub.gradle.enableJsTarget
import com.pubnub.gradle.tasks.GenerateVersionTask
import org.gradle.kotlin.dsl.register
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension

plugins {
alias(libs.plugins.kotlinx.atomicfu) // todo do we need lib for atomic operations
id("pubnub.ios-simulator-test") // start x-code ios simulator before ios test run
id("pubnub.shared")
id("pubnub.dokka")
id("pubnub.multiplatform")
// alias(libs.plugins.mokkery) // todo downgrade version in libs.version.toml to be compatible with used kotlin version(2.0.21)
}

kotlin {
sourceSets {
val commonMain by getting {
dependencies {
api(libs.kotlinx.coroutines.core) // todo this is needed for Service that simulates matchmaking REST api call
implementation(project(":pubnub-matchmaking-kotlin:pubnub-matchmaking-kotlin-api"))
implementation(project(":pubnub-kotlin:pubnub-kotlin-api"))
implementation(libs.kotlinx.atomicfu) // todo this is needed for Service that simulates matchmaking REST api call
implementation(libs.touchlab.kermit)
}
}

val commonTest by getting {
dependencies {
implementation(kotlin("test"))
// implementation(project(":pubnub-chat-test"))
}
}

val jvmMain by getting {
dependencies {
// implementation(project(":pubnub-kotlin")) //todo it shouldn't be required but because of some error might be
implementation(kotlin("test-junit"))
}
}

if (enableJsTarget) {
val jsTest by getting {
dependencies {
implementation(kotlin("test-js")) // to jest potrzebne bo testy KMP na targecie JS nie działały, mimo, że powinny
}
}
}
}

if (enableAnyIosTarget) {
(this as ExtensionAware).extensions.configure<CocoapodsExtension> {
summary = "Some description for a Kotlin/Native module"
homepage = "Link to a Kotlin/Native module homepage"
}
}
}

val generateVersion =
tasks.register<GenerateVersionTask>("generateVersion") {
fileName.set("MatchmakingVersion")
packageName.set("com.pubnub.matchmaking.internal")
constName.set("PUBNUB_MATCHMAKING_VERSION")
version.set(providers.gradleProperty("VERSION_NAME"))
outputDirectory.set(
layout.buildDirectory.map {
it.dir("generated/sources/generateVersion")
},
)
}

kotlin.sourceSets.getByName("commonMain").kotlin.srcDir(generateVersion)
Loading
Loading