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) }