diff --git a/.pubnub.yml b/.pubnub.yml index 3b8c360fb..2103b5e49 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,9 @@ name: java -version: v5.2.3 +version: v5.2.4 schema: 1 scm: github.com/pubnub/java files: - - build/libs/pubnub-gson-5.2.3-all.jar + - build/libs/pubnub-gson-5.2.4-all.jar sdks: - type: library @@ -23,8 +23,8 @@ sdks: - distribution-type: library distribution-repository: git release - package-name: pubnub-gson-5.2.3 - location: https://github.com/pubnub/java/releases/download/v5.2.3/pubnub-gson-5.2.3-all.jar + package-name: pubnub-gson-5.2.4 + location: https://github.com/pubnub/java/releases/download/v5.2.4/pubnub-gson-5.2.4-all.jar supported-platforms: supported-operating-systems: Android: @@ -135,8 +135,8 @@ sdks: - distribution-type: library distribution-repository: maven - package-name: pubnub-gson-5.2.3 - location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-gson/5.2.3/pubnub-gson-5.2.3.jar + package-name: pubnub-gson-5.2.4 + location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-gson/5.2.4/pubnub-gson-5.2.4.jar supported-platforms: supported-operating-systems: Android: @@ -234,6 +234,11 @@ sdks: is-required: Required changelog: + - date: 2021-12-09 + version: v5.2.4 + changes: + - type: bug + text: "Emit PNReconnectedCategory in case of successful manual reconnection." - date: 2021-11-17 version: v5.2.3 changes: diff --git a/CHANGELOG.md b/CHANGELOG.md index b36492216..887e65da5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v5.2.4 +December 09 2021 + +#### Fixed +- Emit PNReconnectedCategory in case of successful manual reconnection. + ## v5.2.3 November 17 2021 diff --git a/README.md b/README.md index c80592f3e..054a3b666 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ You will need the publish and subscribe keys to authenticate your app. Get your com.pubnub pubnub-gson - 5.2.3 + 5.2.4 ``` diff --git a/build.gradle b/build.gradle index f69148076..465fed364 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ plugins { } group = 'com.pubnub' -version = '5.2.3' +version = '5.2.4' description = """""" diff --git a/src/main/java/com/pubnub/api/PubNub.java b/src/main/java/com/pubnub/api/PubNub.java index af492497c..fff93e9e5 100644 --- a/src/main/java/com/pubnub/api/PubNub.java +++ b/src/main/java/com/pubnub/api/PubNub.java @@ -103,7 +103,7 @@ public class PubNub { private static final int TIMESTAMP_DIVIDER = 1000; private static final int MAX_SEQUENCE = 65535; - private static final String SDK_VERSION = "5.2.3"; + private static final String SDK_VERSION = "5.2.4"; private final ListenerManager listenerManager; private final StateManager stateManager; diff --git a/src/main/java/com/pubnub/api/managers/StateManager.java b/src/main/java/com/pubnub/api/managers/StateManager.java index 1c7c6fe04..f4df37ccb 100644 --- a/src/main/java/com/pubnub/api/managers/StateManager.java +++ b/src/main/java/com/pubnub/api/managers/StateManager.java @@ -8,6 +8,7 @@ import com.pubnub.api.builder.dto.SubscribeOperation; import com.pubnub.api.builder.dto.TimetokenAndRegionOperation; import com.pubnub.api.builder.dto.UnsubscribeOperation; +import com.pubnub.api.enums.PNStatusCategory; import com.pubnub.api.models.SubscriptionItem; import lombok.AllArgsConstructor; import lombok.Data; @@ -67,7 +68,7 @@ private static class TemporaryUnavailableItem { private String region = null; private final PNConfiguration configuration; - private boolean shouldAnnounce = false; + private PNStatusCategory announceStatus = null; public StateManager(final PNConfiguration configuration) { this.configuration = configuration; @@ -79,12 +80,12 @@ public synchronized boolean handleOperation(final PubSubOperation... pubSubOpera if (pubSubOperation instanceof SubscribeOperation) { if (adaptSubscribeBuilder((SubscribeOperation) pubSubOperation)) { stateChanged = true; - shouldAnnounce = true; + announceStatus = PNStatusCategory.PNConnectedCategory; } } else if (pubSubOperation instanceof UnsubscribeOperation) { unsubscribe((UnsubscribeOperation) pubSubOperation); stateChanged = true; - shouldAnnounce = true; + announceStatus = PNStatusCategory.PNConnectedCategory; } else if (pubSubOperation instanceof StateOperation) { adaptStateBuilder((StateOperation) pubSubOperation); } else if (pubSubOperation instanceof PresenceOperation) { @@ -98,9 +99,12 @@ public synchronized boolean handleOperation(final PubSubOperation... pubSubOpera } else if (pubSubOperation instanceof ChangeTemporaryUnavailableOperation) { changeTemporary((ChangeTemporaryUnavailableOperation) pubSubOperation); } else if (pubSubOperation instanceof PubSubOperation.ConnectedStatusAnnouncedOperation) { - shouldAnnounce = false; + announceStatus = null; } else if (pubSubOperation instanceof PubSubOperation.ReconnectOperation) { stateChanged = true; + announceStatus = PNStatusCategory.PNReconnectedCategory; + storedTimetoken = timetoken; + timetoken = 0L; } } return stateChanged; @@ -129,7 +133,7 @@ public synchronized SubscriptionStateData subscriptionStateData(Boolean includeP region, hasAnythingToSubscribe(), subscribedToOnlyTemporaryUnavailable(), - shouldAnnounce + announceStatus ); } @@ -474,7 +478,10 @@ public static class SubscriptionStateData { private final String region; private final boolean anythingToSubscribe; private final boolean subscribedToOnlyTemporaryUnavailable; - private final boolean shouldAnnounce; + private final PNStatusCategory announceStatus; + public boolean isShouldAnnounce() { + return announceStatus != null; + } } @Data diff --git a/src/main/java/com/pubnub/api/managers/SubscriptionManager.java b/src/main/java/com/pubnub/api/managers/SubscriptionManager.java index 695b57146..71052d5ad 100644 --- a/src/main/java/com/pubnub/api/managers/SubscriptionManager.java +++ b/src/main/java/com/pubnub/api/managers/SubscriptionManager.java @@ -325,7 +325,7 @@ synchronized void startSubscribeLoop(final PubSubOperation... pubSubOperations) final PubSubOperation statusAnnouncedOperation; if (subscriptionStateData.isShouldAnnounce()) { PNStatus pnStatus = createPublicStatus(status) - .category(PNStatusCategory.PNConnectedCategory) + .category(subscriptionStateData.getAnnounceStatus()) .error(false) .build(); listenerManager.announce(pnStatus); diff --git a/src/test/java/com/pubnub/api/PubNubTest.java b/src/test/java/com/pubnub/api/PubNubTest.java index 539af9092..f70bbe495 100644 --- a/src/test/java/com/pubnub/api/PubNubTest.java +++ b/src/test/java/com/pubnub/api/PubNubTest.java @@ -99,7 +99,7 @@ public void getVersionAndTimeStamp() { pubnub = new PubNub(pnConfiguration); String version = pubnub.getVersion(); int timeStamp = pubnub.getTimestamp(); - Assert.assertEquals("5.2.3", version); + Assert.assertEquals("5.2.4", version); Assert.assertTrue(timeStamp > 0); } diff --git a/src/test/java/com/pubnub/api/managers/FastSubscriptionManagerTest.java b/src/test/java/com/pubnub/api/managers/FastSubscriptionManagerTest.java index fdb149e54..19c0d67fe 100644 --- a/src/test/java/com/pubnub/api/managers/FastSubscriptionManagerTest.java +++ b/src/test/java/com/pubnub/api/managers/FastSubscriptionManagerTest.java @@ -4,34 +4,45 @@ import com.pubnub.api.builder.dto.ChangeTemporaryUnavailableOperation; import com.pubnub.api.builder.dto.PubSubOperation; import com.pubnub.api.builder.dto.SubscribeOperation; +import com.pubnub.api.enums.PNStatusCategory; import com.pubnub.api.managers.subscription.utils.RequestDetails; import com.pubnub.api.managers.subscription.utils.ResponseHolder; import com.pubnub.api.managers.subscription.utils.ResponseSupplier; import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNStatus; import com.pubnub.api.models.server.SubscribeEnvelope; import com.pubnub.api.models.server.SubscribeMetadata; import com.pubnub.api.services.SubscribeService; import okhttp3.MediaType; import okhttp3.ResponseBody; import org.awaitility.core.ThrowingRunnable; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; import org.jetbrains.annotations.NotNull; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import retrofit2.Response; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import static com.pubnub.api.managers.subscription.utils.SubscriptionTestUtils.pubnub; import static com.pubnub.api.managers.subscription.utils.SubscriptionTestUtils.retrofitManagerMock; import static com.pubnub.api.managers.subscription.utils.SubscriptionTestUtils.telemetryManager; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; @@ -253,6 +264,76 @@ public void run() throws Throwable { }); } + @Test + public void sendReconnectedOnManualReconnect() { + long timeToken = System.currentTimeMillis(); + final ResponseSupplier responseSupplier = new ResponseSupplier() { + private boolean first = true; + + @Override + public ResponseHolder get(final RequestDetails requestDetails) { + try { + MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + } + if (first) { + first = false; + return new ResponseHolder<>(Response.error(500, ResponseBody.create(MediaType.parse("application/json"), "{}"))); + } + final SubscribeEnvelope subscribeEnvelope = new SubscribeEnvelope(emptyList(), + new SubscribeMetadata(timeToken, FAKE_REGION)); + return new ResponseHolder<>(Response.success(200, subscribeEnvelope)); + } + }; + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + final SubscriptionManager subscriptionManager = subscriptionManagerUnderTest(retrofitManagerMock); + + final SubscribeOperation subscribeOperation = SubscribeOperation.builder() + .channels(singletonList("ch2")) + .channelGroups(singletonList("group2")) + .build(); + + subscriptionManager.adaptSubscribeBuilder(subscribeOperation); + subscriptionManager.reconnect(); + ArgumentCaptor statusArgumentCaptor = ArgumentCaptor.forClass(PNStatus.class); + await().atMost(1, SECONDS).untilAsserted(() -> verify(listenerManagerMock, times(2)).announce(statusArgumentCaptor.capture())); + + List statusCategories = statusArgumentCaptor.getAllValues().stream().map(PNStatus::getCategory).collect(Collectors.toList()); + MatcherAssert.assertThat(statusCategories, Matchers.hasItem(PNStatusCategory.PNReconnectedCategory)); + } + + @Test + public void reconnectUsesTT0ForFastUserNotification() { + final ResponseSupplier responseSupplier = requestDetails -> { + final SubscribeEnvelope subscribeEnvelope = new SubscribeEnvelope(emptyList(), + new SubscribeMetadata(System.currentTimeMillis(), FAKE_REGION)); + try { + MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + } + return new ResponseHolder<>(Response.success(200, subscribeEnvelope)); + }; + + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + final SubscribeService spiedSubscribeService = retrofitManagerMock.getSubscribeService(); + final SubscriptionManager subscriptionManager = subscriptionManagerUnderTest(retrofitManagerMock); + + final SubscribeOperation subscribeOperation = SubscribeOperation.builder() + .channels(singletonList("ch2")) + .channelGroups(singletonList("group2")) + .timetoken(42L) + .build(); + + subscriptionManager.adaptSubscribeBuilder(subscribeOperation); + await().atMost(1, SECONDS).untilAsserted(() -> verify(spiedSubscribeService, atLeast(2)).subscribe(any(), any(), any())); + subscriptionManager.reconnect(); + ArgumentCaptor> queryParamsCaptor = ArgumentCaptor.forClass(Map.class); + await().atMost(1, SECONDS).untilAsserted(() -> verify(spiedSubscribeService, atLeast(1)).subscribe(any(), any(), queryParamsCaptor.capture())); + + List statusCategories = queryParamsCaptor.getAllValues().stream().map((map) -> map.get("tt")).collect(Collectors.toList()); + MatcherAssert.assertThat(statusCategories, Matchers.hasItem("0")); + } + @NotNull private SubscriptionManager subscriptionManagerUnderTest(final RetrofitManager retrofitManagerMock) { final PubNub pubnub = spy(pubnub(retrofitManagerMock));