diff --git a/.github/workflows/commands-handler.yml b/.github/workflows/commands-handler.yml
index b91633dea..497fc880e 100644
--- a/.github/workflows/commands-handler.yml
+++ b/.github/workflows/commands-handler.yml
@@ -35,6 +35,9 @@ jobs:
ref: v1
token: ${{ secrets.GH_TOKEN }}
path: .github/.release/actions
+ - name: Update CocoaPods trunk
+ run: |
+ pod repo update
- name: Process changelog entries
if: steps.user-check.outputs.expected-user == 'true'
uses: ./.github/.release/actions/actions/commands
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b95148b64..d2716a030 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -55,6 +55,9 @@ jobs:
ref: v1
token: ${{ secrets.GH_TOKEN }}
path: .github/.release/actions
+ - name: Update CocoaPods trunk
+ run: |
+ pod repo update
- name: Publish to Maven
uses: ./.github/.release/actions/actions/services/maven
with:
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 6c1774612..d22720800 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -33,6 +33,9 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
+ - name: Update CocoaPods trunk
+ run: |
+ pod repo update
- name: Build and run tests
run: |
./gradlew check
diff --git a/.github/workflows/run-validations.yml b/.github/workflows/run-validations.yml
index c0ec1c645..1969875e8 100644
--- a/.github/workflows/run-validations.yml
+++ b/.github/workflows/run-validations.yml
@@ -53,6 +53,9 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
+ - name: Update CocoaPods trunk
+ run: |
+ pod repo update
- name: Validate clean build
run: ./gradlew ktlintFormat publishAllPublicationsToRepoRepository -PRELEASE_SIGNING_ENABLED=false
- name: Cancel workflow runs for commit on error
diff --git a/.pubnub.yml b/.pubnub.yml
index 95545d380..8c4cf73cb 100644
--- a/.pubnub.yml
+++ b/.pubnub.yml
@@ -1,9 +1,9 @@
name: kotlin
-version: 10.3.4
+version: 10.4.0
schema: 1
scm: github.com/pubnub/kotlin
files:
- - build/libs/pubnub-kotlin-10.3.4-all.jar
+ - build/libs/pubnub-kotlin-10.4.0-all.jar
sdks:
-
type: library
@@ -23,8 +23,8 @@ sdks:
-
distribution-type: library
distribution-repository: maven
- package-name: pubnub-kotlin-10.3.4
- location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-kotlin/10.3.4/pubnub-kotlin-10.3.4.jar
+ package-name: pubnub-kotlin-10.4.0
+ location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-kotlin/10.4.0/pubnub-kotlin-10.4.0.jar
supported-platforms:
supported-operating-systems:
Android:
@@ -121,6 +121,11 @@ sdks:
license-url: https://www.apache.org/licenses/LICENSE-2.0.txt
is-required: Required
changelog:
+ - date: 2025-01-21
+ version: v10.4.0
+ changes:
+ - type: feature
+ text: "A new optional parameter `ifMatchesEtag` is added to `setUUIDMetadata` and `setChannelMetadata`. When provided, the server compares the argument value with the ETag on the server and if they don't match a HTTP 412 error is returned."
- date: 2025-01-13
version: v10.3.4
changes:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2229f40cd..afced012d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## v10.4.0
+January 21 2025
+
+#### Added
+- A new optional parameter `ifMatchesEtag` is added to `setUUIDMetadata` and `setChannelMetadata`. When provided, the server compares the argument value with the ETag on the server and if they don't match a HTTP 412 error is returned.
+
## v10.3.4
January 13 2025
diff --git a/README.md b/README.md
index 83d8eec7f..fd7ae2d43 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ You will need the publish and subscribe keys to authenticate your app. Get your
com.pubnub
pubnub-kotlin
- 10.3.4
+ 10.4.0
```
diff --git a/gradle.properties b/gradle.properties
index a135697ee..67bebf334 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,7 +18,7 @@ RELEASE_SIGNING_ENABLED=true
SONATYPE_HOST=DEFAULT
SONATYPE_AUTOMATIC_RELEASE=false
GROUP=com.pubnub
-VERSION_NAME=10.3.4
+VERSION_NAME=10.4.0
POM_PACKAGING=jar
POM_NAME=PubNub SDK
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 72acd5f2e..52160e2ed 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -12,8 +12,8 @@ ktlint = "12.1.0"
dokka = "1.9.20"
kotlinx_datetime = "0.6.1"
kotlinx_coroutines = "1.9.0"
-pubnub_js = "8.4.1"
-pubnub_swift = "8.2.4"
+pubnub_js = "8.6.0"
+pubnub_swift = "8.3.0"
[libraries]
retrofit2 = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit2" }
diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock
index ba1a895d3..0f40a72b5 100644
--- a/kotlin-js-store/yarn.lock
+++ b/kotlin-js-store/yarn.lock
@@ -659,10 +659,26 @@ proxy-from-env@^1.1.0:
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
-pubnub@8.4.0:
- version "8.4.0"
- resolved "https://registry.yarnpkg.com/pubnub/-/pubnub-8.4.0.tgz#8a5fdd3af9783abb1de44ea4145107e296c8394d"
- integrity sha512-HvkFhn4XcfR1wdJv4paX94I0TT4UBHW/vIuYnS+6XuoSx0AunJMCk5wbKnSesmdbzYUZa8Ag3H1mJQZKuyRy8Q==
+pubnub@8.4.1:
+ version "8.4.1"
+ resolved "https://registry.yarnpkg.com/pubnub/-/pubnub-8.4.1.tgz#5f6f19e84d3187dc8aee0a458bd6b05e90d43e6a"
+ integrity sha512-mPlwVoHJDWPasZx52UfSMiPX5TATm5A+ficSogyqDqTQ4w5EQnwxH+PJdsWc0mPnlT051jM1vIISMeM0fQ30CQ==
+ dependencies:
+ agentkeepalive "^3.5.2"
+ buffer "^6.0.3"
+ cbor-js "^0.1.0"
+ cbor-sync "^1.0.4"
+ form-data "^4.0.0"
+ lil-uuid "^0.1.1"
+ node-fetch "^2.7.0"
+ proxy-agent "^6.3.0"
+ react-native-url-polyfill "^2.0.0"
+ text-encoding "^0.7.0"
+
+pubnub@8.6.0:
+ version "8.6.0"
+ resolved "https://registry.yarnpkg.com/pubnub/-/pubnub-8.6.0.tgz#75524e7ed3653090652d160ce83ac089362a0379"
+ integrity sha512-LBCglooxiLkNT3ArUOvSJnLKK6/QdeshWY60IWlSQ+SkXlzEjt74wAnX5XriEXKsmza2yw9mFGG6+B5SlczRzA==
dependencies:
agentkeepalive "^3.5.2"
buffer "^6.0.3"
diff --git a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/channel/SetChannelMetadata.java b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/channel/SetChannelMetadata.java
index 22ebe63fe..2a3fa429c 100644
--- a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/channel/SetChannelMetadata.java
+++ b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/channel/SetChannelMetadata.java
@@ -3,6 +3,7 @@
import com.pubnub.api.java.endpoints.BuilderSteps;
import com.pubnub.api.java.endpoints.Endpoint;
import com.pubnub.api.java.models.consumer.objects_api.channel.PNSetChannelMetadataResult;
+import org.jetbrains.annotations.Nullable;
import java.util.Map;
@@ -19,6 +20,14 @@ public interface SetChannelMetadata extends Endpoint
SetChannelMetadata includeCustom(boolean includeCustom);
+ /**
+ * Optional entity tag from a previously received `PNChannelMetadata`. The request
+ * will fail if this parameter is specified and the ETag value on the server doesn't match.
+ * @param etag from PNChannelMetadata
+ * @return this builder
+ */
+ SetChannelMetadata ifMatchesEtag(@Nullable String etag);
+
interface Builder extends BuilderSteps.ChannelStep {
@Override
SetChannelMetadata channel(String channel);
diff --git a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/uuid/SetUUIDMetadata.java b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/uuid/SetUUIDMetadata.java
index 7e4520ef1..32b0022b2 100644
--- a/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/uuid/SetUUIDMetadata.java
+++ b/pubnub-gson/pubnub-gson-api/src/main/java/com/pubnub/api/java/endpoints/objects_api/uuid/SetUUIDMetadata.java
@@ -2,6 +2,7 @@
import com.pubnub.api.java.endpoints.Endpoint;
import com.pubnub.api.java.models.consumer.objects_api.uuid.PNSetUUIDMetadataResult;
+import org.jetbrains.annotations.Nullable;
import java.util.Map;
@@ -23,4 +24,12 @@ public interface SetUUIDMetadata extends Endpoint {
SetUUIDMetadata type(String type);
SetUUIDMetadata status(String status);
+
+ /**
+ * Optional entity tag from a previously received `PNUUIDMetadata`. The request
+ * will fail if this parameter is specified and the ETag value on the server doesn't match.
+ * @param etag from PNUUIDMetadata
+ * @return this builder
+ */
+ SetUUIDMetadata ifMatchesEtag(@Nullable String etag);
}
diff --git a/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/channel/SetChannelMetadataImpl.java b/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/channel/SetChannelMetadataImpl.java
index f94462105..eb2c90a6a 100644
--- a/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/channel/SetChannelMetadataImpl.java
+++ b/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/channel/SetChannelMetadataImpl.java
@@ -12,6 +12,7 @@
import lombok.Setter;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
@@ -46,7 +47,8 @@ protected Endpoint createRemoteAction() {
custom,
includeCustom,
type,
- status
+ status,
+ ifMatchesEtag
);
}
@@ -69,6 +71,10 @@ protected Endpoint createRemoteAction() {
@Setter
private boolean includeCustom;
+ @Setter
+ @Nullable
+ private String ifMatchesEtag;
+
@Override
public SetChannelMetadata custom(Map custom) {
final HashMap customHashMap = new HashMap<>();
diff --git a/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/uuid/SetUUIDMetadataImpl.java b/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/uuid/SetUUIDMetadataImpl.java
index 036142032..fbc047ac9 100644
--- a/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/uuid/SetUUIDMetadataImpl.java
+++ b/pubnub-gson/pubnub-gson-impl/src/main/java/com/pubnub/internal/java/endpoints/objects_api/uuid/SetUUIDMetadataImpl.java
@@ -12,6 +12,7 @@
import lombok.Setter;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
@@ -37,6 +38,10 @@ public class SetUUIDMetadataImpl extends DelegatingEndpoint createRemoteAction() {
custom,
includeCustom,
type,
- status
+ status,
+ ifMatchesEtag
);
}
diff --git a/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml b/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml
index 30a4af6d9..f3b669e20 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml
+++ b/pubnub-kotlin/pubnub-kotlin-api/config/ktlint/baseline.xml
@@ -6,9 +6,6 @@
-
-
-
@@ -22,19 +19,17 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -46,8 +41,8 @@
-
-
+
+
@@ -82,8 +77,7 @@
-
-
-
+
+
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/PubNubImpl.kt b/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/PubNubImpl.kt
index bc6c4b0da..36edaf577 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/PubNubImpl.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/PubNubImpl.kt
@@ -513,7 +513,8 @@ class PubNubImpl(private val pubNubObjC: KMPPubNub) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
- status: String?
+ status: String?,
+ ifMatchesEtag: String?,
): SetChannelMetadata {
return SetChannelMetadataImpl(
pubnub = pubNubObjC,
@@ -523,7 +524,8 @@ class PubNubImpl(private val pubNubObjC: KMPPubNub) : PubNub {
custom = custom,
includeCustom = includeCustom,
type = type,
- status = status
+ status = status,
+ ifMatchesEtag = ifMatchesEtag,
)
}
@@ -570,7 +572,8 @@ class PubNubImpl(private val pubNubObjC: KMPPubNub) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
- status: String?
+ status: String?,
+ ifMatchesEtag: String?,
): SetUUIDMetadata {
return SetUUIDMetadataImpl(
pubnub = pubNubObjC,
@@ -582,7 +585,8 @@ class PubNubImpl(private val pubNubObjC: KMPPubNub) : PubNub {
custom = custom,
includeCustom = includeCustom,
type = type,
- status = status
+ status = status,
+ ifMatchesEtag = ifMatchesEtag,
)
}
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/channel/SetChannelMetadata.ios.kt b/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/channel/SetChannelMetadata.ios.kt
index a06571320..b95c9cddc 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/channel/SetChannelMetadata.ios.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/channel/SetChannelMetadata.ios.kt
@@ -28,7 +28,8 @@ class SetChannelMetadataImpl(
private val custom: CustomObject?,
private val includeCustom: Boolean,
private val type: String?,
- private val status: String?
+ private val status: String?,
+ private val ifMatchesEtag: String? = null,
) : SetChannelMetadata {
override fun async(callback: Consumer>) {
pubnub.setChannelMetadataWithMetadataId(
@@ -43,6 +44,7 @@ class SetChannelMetadataImpl(
),
type = type,
status = status,
+ ifMatchesEtag = ifMatchesEtag,
onSuccess = callback.onSuccessHandler {
PNChannelMetadataResult(
status = 200,
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/uuid/SetUUIDMetadata.ios.kt b/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/uuid/SetUUIDMetadata.ios.kt
index f5904b6a9..b977b7589 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/uuid/SetUUIDMetadata.ios.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/appleMain/kotlin/com/pubnub/api/endpoints/objects/uuid/SetUUIDMetadata.ios.kt
@@ -30,7 +30,8 @@ class SetUUIDMetadataImpl(
private val custom: CustomObject?,
private val includeCustom: Boolean,
private val type: String?,
- private val status: String?
+ private val status: String?,
+ private val ifMatchesEtag: String? = null,
) : SetUUIDMetadata {
override fun async(callback: Consumer>) {
pubnub.setUserMetadataWithMetadataId(
@@ -47,6 +48,7 @@ class SetUUIDMetadataImpl(
),
type = type,
status = status,
+ ifMatchesEtag = ifMatchesEtag,
onSuccess = callback.onSuccessHandler {
PNUUIDMetadataResult(
status = 200,
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt
index b0a8f397d..2a0a6b351 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonMain/kotlin/com/pubnub/api/PubNub.kt
@@ -268,6 +268,7 @@ expect interface PubNub {
includeCustom: Boolean = false,
type: String? = null,
status: String? = null,
+ ifMatchesEtag: String? = null,
): SetChannelMetadata
fun removeChannelMetadata(channel: String): RemoveChannelMetadata
@@ -296,6 +297,7 @@ expect interface PubNub {
includeCustom: Boolean = false,
type: String? = null,
status: String? = null,
+ ifMatchesEtag: String? = null,
): SetUUIDMetadata
fun removeUUIDMetadata(uuid: String? = null): RemoveUUIDMetadata
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt
index 49cc63043..6ca3f1fbd 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/ChannelMetadataTest.kt
@@ -52,6 +52,58 @@ class ChannelMetadataTest : BaseIntegrationTest() {
assertEquals(description, pnuuidMetadata.description?.value)
}
+ @Test
+ fun set_metadata_ifMatch_allows_change() = runTest {
+ // given
+ val result = pubnub.setChannelMetadata(
+ channel,
+ name = name,
+ status = status,
+ custom = custom,
+ includeCustom = includeCustom,
+ type = type,
+ description = description
+ ).await()
+
+ val pnChannelMetadata = result.data
+
+ // when
+ val newData = pubnub.setChannelMetadata(
+ channel,
+ status = "someNewStatus",
+ ifMatchesEtag = pnChannelMetadata.eTag?.value
+ ).await().data
+
+ // then
+ assertEquals("someNewStatus", newData.status?.value)
+ }
+
+ @Test
+ fun set_metadata_ifMatch_prohibits_change() = runTest {
+ // given
+ val result = pubnub.setChannelMetadata(
+ channel,
+ name = name,
+ status = status,
+ custom = custom,
+ includeCustom = includeCustom,
+ type = type,
+ description = description
+ ).await()
+
+ val pnChannelMetadata = result.data
+
+ pubnub.setChannelMetadata(channel, name = "someNewName").await()
+
+ // when
+ val ex = assertFailsWith {
+ pubnub.setChannelMetadata(channel, status = "someNewStatus", ifMatchesEtag = pnChannelMetadata.eTag?.value).await()
+ }
+
+ // then
+ assertEquals(HTTP_PRECONDITION_FAILED, ex.statusCode)
+ }
+
@Test
fun can_receive_set_metadata_event() = runTest {
pubnub.test(backgroundScope) {
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt
index daeafec59..c0473f7f4 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/commonTest/kotlin/com/pubnub/test/integration/UserMetadataTest.kt
@@ -20,6 +20,8 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.time.Duration.Companion.seconds
+internal const val HTTP_PRECONDITION_FAILED = 412
+
class UserMetadataTest : BaseIntegrationTest() {
private val uuid = "myUser" + randomString()
private val name = randomString()
@@ -60,6 +62,57 @@ class UserMetadataTest : BaseIntegrationTest() {
assertEquals(type, pnuuidMetadata.type?.value)
}
+ @Test
+ fun set_metadata_ifMatch_allows_change() = runTest {
+ // given
+ val result = pubnub.setUUIDMetadata(
+ uuid,
+ name = name,
+ externalId = externalId,
+ profileUrl = profileUrl,
+ email = email,
+ status = status,
+ custom = custom,
+ includeCustom = includeCustom,
+ type = type
+ ).await()
+
+ val pnuuidMetadata = result.data
+
+ // when
+ val newData = pubnub.setUUIDMetadata(uuid, externalId = "someNewId", ifMatchesEtag = pnuuidMetadata.eTag?.value).await().data
+
+ // then
+ assertEquals("someNewId", newData.externalId?.value)
+ }
+
+ @Test
+ fun set_metadata_ifMatch_prohibits_change() = runTest {
+ // given
+ val result = pubnub.setUUIDMetadata(
+ uuid,
+ name = name,
+ externalId = externalId,
+ profileUrl = profileUrl,
+ email = email,
+ status = status,
+ custom = custom,
+ includeCustom = includeCustom,
+ type = type
+ ).await()
+
+ val pnuuidMetadata = result.data
+ pubnub.setUUIDMetadata(uuid, name = "someNewName").await()
+
+ // when
+ val ex = assertFailsWith {
+ pubnub.setUUIDMetadata(uuid, externalId = "someNewId", ifMatchesEtag = pnuuidMetadata.eTag?.value).await()
+ }
+
+ // then
+ assertEquals(HTTP_PRECONDITION_FAILED, ex.statusCode)
+ }
+
@Test
fun can_receive_set_metadata_event() = runTest {
pubnub.test(backgroundScope) {
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/Pubnub.d.kt b/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/Pubnub.d.kt
index 774f4bafe..b1ee8c00e 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/Pubnub.d.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/Pubnub.d.kt
@@ -1070,6 +1070,7 @@ open external class PubNub(config: Any /* UUID | UserId */) {
var data: UUIDMetadata
var include: UuidIncludeCustom?
+ var ifMatchesEtag: String?
}
interface RemoveUUIDMetadataParameters {
@@ -1121,6 +1122,7 @@ open external class PubNub(config: Any /* UUID | UserId */) {
var channel: String
var data: ChannelMetadata
var include: UuidIncludeCustom?
+ var ifMatchesEtag: String?
}
interface RemoveChannelMetadataParameters {
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/com/pubnub/api/PubNubImpl.kt b/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/com/pubnub/api/PubNubImpl.kt
index 3229e07b8..7adb3343a 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/com/pubnub/api/PubNubImpl.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/jsMain/kotlin/com/pubnub/api/PubNubImpl.kt
@@ -563,7 +563,8 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
- status: String?
+ status: String?,
+ ifMatchesEtag: String?,
): SetChannelMetadata {
return SetChannelMetadataImpl(
jsPubNub,
@@ -576,6 +577,7 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
PatchValue.of(type),
PatchValue.of(custom),
)
+ this.ifMatchesEtag = ifMatchesEtag
this.include = createJsObject {
this.customFields = includeCustom
@@ -637,7 +639,8 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
- status: String?
+ status: String?,
+ ifMatchesEtag: String?,
): SetUUIDMetadata {
return SetUUIDMetadataImpl(
jsPubNub,
@@ -652,6 +655,7 @@ class PubNubImpl(val jsPubNub: PubNubJs) : PubNub {
PatchValue.of(custom),
)
this.uuid = uuid
+ this.ifMatchesEtag = ifMatchesEtag
include = createJsObject {
this.customFields = includeCustom
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt b/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt
index 27a9901d8..9b4624b45 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/jvmMain/kotlin/com/pubnub/api/PubNub.kt
@@ -1142,6 +1142,8 @@ actual interface PubNub : StatusEmitter, EventEmitter {
* @param description Description of a channel.
* @param custom Object with supported data types.
* @param includeCustom Include respective additional fields in the response.
+ * @param ifMatchesEtag Optional entity tag from a previously received `PNChannelMetadata`. The request
+ * will fail if this parameter is specified and the ETag value on the server doesn't match.
*/
actual fun setChannelMetadata(
channel: String,
@@ -1151,6 +1153,7 @@ actual interface PubNub : StatusEmitter, EventEmitter {
includeCustom: Boolean,
type: String?,
status: String?,
+ ifMatchesEtag: String?,
): SetChannelMetadata
/**
@@ -1208,6 +1211,8 @@ actual interface PubNub : StatusEmitter, EventEmitter {
* @param email The user's email address. Maximum 80 characters.
* @param custom Object with supported data types.
* @param includeCustom Include respective additional fields in the response.
+ * @param ifMatchesEtag Optional entity tag from a previously received `PNUUIDMetadata`. The request
+ * will fail if this parameter is specified and the ETag value on the server doesn't match.
*/
actual fun setUUIDMetadata(
uuid: String?,
@@ -1219,6 +1224,7 @@ actual interface PubNub : StatusEmitter, EventEmitter {
includeCustom: Boolean,
type: String?,
status: String?,
+ ifMatchesEtag: String?,
): SetUUIDMetadata
/**
diff --git a/pubnub-kotlin/pubnub-kotlin-api/src/nonJvm/kotlin/com/pubnub/api/PubNub.nonJvm.kt b/pubnub-kotlin/pubnub-kotlin-api/src/nonJvm/kotlin/com/pubnub/api/PubNub.nonJvm.kt
index cbbb85cb9..ade42391e 100644
--- a/pubnub-kotlin/pubnub-kotlin-api/src/nonJvm/kotlin/com/pubnub/api/PubNub.nonJvm.kt
+++ b/pubnub-kotlin/pubnub-kotlin-api/src/nonJvm/kotlin/com/pubnub/api/PubNub.nonJvm.kt
@@ -247,7 +247,8 @@ actual interface PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
- status: String?
+ status: String?,
+ ifMatchesEtag: String?,
): SetChannelMetadata
actual fun removeChannelMetadata(channel: String): RemoveChannelMetadata
@@ -275,7 +276,8 @@ actual interface PubNub {
custom: CustomObject?,
includeCustom: Boolean,
type: String?,
- status: String?
+ status: String?,
+ ifMatchesEtag: String?
): SetUUIDMetadata
actual fun removeUUIDMetadata(uuid: String?): RemoveUUIDMetadata
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt
index 0c3f7aaff..305f085da 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/PubNubImpl.kt
@@ -756,6 +756,7 @@ open class PubNubImpl(
includeCustom: Boolean,
type: String?,
status: String?,
+ ifMatchesEtag: String?,
): SetChannelMetadata {
return SetChannelMetadataEndpoint(
pubnub = this,
@@ -766,6 +767,7 @@ open class PubNubImpl(
includeQueryParam = IncludeQueryParam(includeCustom = includeCustom),
type = type,
status = status,
+ ifMatchesEtag = ifMatchesEtag,
)
}
@@ -816,6 +818,7 @@ open class PubNubImpl(
includeCustom: Boolean,
type: String?,
status: String?,
+ ifMatchesEtag: String?,
): SetUUIDMetadata {
return SetUUIDMetadataEndpoint(
pubnub = this,
@@ -828,6 +831,7 @@ open class PubNubImpl(
withInclude = IncludeQueryParam(includeCustom = includeCustom),
type = type,
status = status,
+ ifMatchesEtag = ifMatchesEtag,
)
}
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/channel/SetChannelMetadataEndpoint.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/channel/SetChannelMetadataEndpoint.kt
index 1e9d8b497..a89ac1a6c 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/channel/SetChannelMetadataEndpoint.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/channel/SetChannelMetadataEndpoint.kt
@@ -25,6 +25,7 @@ class SetChannelMetadataEndpoint internal constructor(
private val includeQueryParam: IncludeQueryParam,
private val type: String?,
private val status: String?,
+ private val ifMatchesEtag: String?,
) : EndpointCore, PNChannelMetadataResult>(pubnub),
SetChannelMetadata {
override fun doWork(queryParams: HashMap): Call> {
@@ -41,6 +42,7 @@ class SetChannelMetadataEndpoint internal constructor(
),
channel = channel,
options = params,
+ ifMatchesEtag = ifMatchesEtag
)
}
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/uuid/SetUUIDMetadataEndpoint.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/uuid/SetUUIDMetadataEndpoint.kt
index 375618ae5..6d00fc11b 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/uuid/SetUUIDMetadataEndpoint.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/objects/uuid/SetUUIDMetadataEndpoint.kt
@@ -27,6 +27,7 @@ class SetUUIDMetadataEndpoint internal constructor(
private val withInclude: IncludeQueryParam,
private val type: String?,
private val status: String?,
+ private val ifMatchesEtag: String?,
) : EndpointCore, PNUUIDMetadataResult>(pubnub), SetUUIDMetadata {
override fun doWork(queryParams: HashMap): Call> {
val params = queryParams + withInclude.createIncludeQueryParams()
@@ -44,6 +45,7 @@ class SetUUIDMetadataEndpoint internal constructor(
),
uuid = uuid ?: configuration.userId.value,
options = params,
+ ifMatchesEtag = ifMatchesEtag
)
}
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/services/ObjectsService.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/services/ObjectsService.kt
index b9d63b4fd..57a152fa0 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/services/ObjectsService.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/services/ObjectsService.kt
@@ -14,6 +14,7 @@ import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
+import retrofit2.http.Header
import retrofit2.http.Headers
import retrofit2.http.PATCH
import retrofit2.http.Path
@@ -40,6 +41,7 @@ internal interface ObjectsService {
@Path("uuid") uuid: String,
@Body body: UUIDMetadataInput,
@QueryMap(encoded = true) options: Map = mapOf(),
+ @Header("If-Match") ifMatchesEtag: String? = null,
): Call>
@DELETE("v2/objects/{subKey}/uuids/{uuid}")
@@ -68,6 +70,7 @@ internal interface ObjectsService {
@Path("channel") channel: String,
@Body body: ChannelMetadataInput,
@QueryMap(encoded = true) options: Map = mapOf(),
+ @Header("If-Match") ifMatchesEtag: String? = null,
): Call>
@DELETE("v2/objects/{subKey}/channels/{channel}")
diff --git a/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt b/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt
index 1fec4de8b..40a7cc1da 100644
--- a/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt
+++ b/pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/legacy/PubNubImplTest.kt
@@ -56,7 +56,7 @@ class PubNubImplTest : BaseTest() {
fun getVersionAndTimeStamp() {
val version = PubNubImpl.SDK_VERSION
val timeStamp = PubNubImpl.timestamp()
- assertEquals("10.3.4", version)
+ assertEquals("10.4.0", version)
assertTrue(timeStamp > 0)
}