diff --git a/src/PubNub/Endpoints/MessagePersistance/FetchMessages.php b/src/PubNub/Endpoints/MessagePersistance/FetchMessages.php new file mode 100644 index 00000000..098a457d --- /dev/null +++ b/src/PubNub/Endpoints/MessagePersistance/FetchMessages.php @@ -0,0 +1,218 @@ + 'start', + 'end' => 'end', + 'count' => 'max', + 'includeMeta' => 'include_meta', + 'includeUuid' => 'include_uuid', + 'includeMessageType' => 'include_message_type', + ]; + + public function channels(...$channel) + { + if (is_array($channel[0])) { + $this->channels = $channel[0]; + } elseif (strpos($channel[0], ',')) { + $this->channels = array_map('trim', explode(',', $channel[0])); + } else { + $this->channels = $channel; + } + return $this; + } + + public function start($start) + { + $this->start = $start; + return $this; + } + + public function end($end) + { + $this->end = $end; + return $this; + } + + public function count($count) + { + $this->count = $count; + return $this; + } + + public function includeMeta($includeMeta) + { + $this->includeMeta = $includeMeta; + return $this; + } + + public function includeUuid($includeUuid) + { + $this->includeUuid = $includeUuid; + return $this; + } + + public function includeMessageType($includeMessageType) + { + $this->includeMessageType = $includeMessageType; + return $this; + } + + public function includeMessageActions($includeMessageActions) + { + $this->includeMessageActions = $includeMessageActions; + return $this; + } + + /** + * @throws PubNubValidationException + */ + protected function validateParams() + { + if (!is_array($this->channels) || count($this->channels) === 0) { + throw new PubNubValidationException("Channel Missing"); + } + + $this->validateSubscribeKey(); + $this->validatePublishKey(); + } + + /** + * @return array + */ + protected function customParams() + { + $params = []; + foreach ($this->customParamMapping as $customParam => $requestParam) { + if (isset($this->$customParam) && !empty($this->$customParam)) { + $params[$requestParam] = $this->$customParam; + } + } + + return $params; + } + + /** + * @return string + * @throws PubNubBuildRequestException + */ + protected function buildPath() + { + $withActions = $this->includeMessageActions ? '-with-actions' : ''; + $channelList = $this->includeMessageActions + ? PubNubUtil::urlEncode($this->channels[0]) + : implode(',', array_map(fn($channel) => PubNubUtil::urlEncode($channel), $this->channels)); + + return sprintf( + self::GET_PATH, + $withActions, + $this->pubnub->getConfiguration()->getSubscribeKey(), + $channelList, + ); + } + + public function sync(): PNFetchMessagesResult + { + return parent::sync(); + } + + /** + * @param array $json Decoded json + * @return PNPublishResult + */ + protected function createResponse($json) + { + return PNFetchMessagesResult::fromJson( + $json, + $this->pubnub->getCrypto(), + isset($this->start) ? $this->start : null, + isset($this->end) ? $this->end : null + ); + } + + /** + * @return bool + */ + protected function isAuthRequired() + { + return true; + } + + protected function buildData() + { + return null; + } + + /** + * @return int + */ + protected function getRequestTimeout() + { + return $this->pubnub->getConfiguration()->getNonSubscribeRequestTimeout(); + } + + /** + * @return int + */ + protected function getConnectTimeout() + { + return $this->pubnub->getConfiguration()->getConnectTimeout(); + } + + /** + * @return string + */ + protected function httpMethod() + { + return PNHttpMethod::GET; + } + + /** + * @return int + */ + protected function getOperationType() + { + return PNOperationType::PNFetchMessagesOperation; + } + + /** + * @return string + */ + protected function getName() + { + return "Fetch Messages"; + } +} diff --git a/src/PubNub/Models/Consumer/MessagePersistence/PNFetchMessagesItemResult.php b/src/PubNub/Models/Consumer/MessagePersistence/PNFetchMessagesItemResult.php new file mode 100644 index 00000000..c65deb55 --- /dev/null +++ b/src/PubNub/Models/Consumer/MessagePersistence/PNFetchMessagesItemResult.php @@ -0,0 +1,111 @@ +message = $message; + $this->timetoken = $timetoken; + } + + public function setMetadata(mixed $metadata) + { + $this->metadata = $metadata; + return $this; + } + + public function setActions(mixed $actions) + { + $this->actions = $actions; + return $this; + } + + public function setUuid(string $uuid) + { + $this->uuid = $uuid; + return $this; + } + + public function setMessageType(string $messageType) + { + $this->messageType = $messageType; + return $this; + } + + public function getMessage(): mixed + { + return $this->message; + } + + public function getTimetoken(): string + { + return $this->timetoken; + } + + public function getMetadata(): mixed + { + return $this->metadata; + } + + public function getActions(): mixed + { + return $this->actions; + } + + public function getUuid(): string + { + return $this->uuid; + } + + public function getMessageType(): string + { + return $this->messageType; + } + + public static function fromJson($json, $crypto): static + { + $message = $json['message']; + if ($crypto) { + $message = $crypto->decrypt($message); + } + $item = new static( + $message, + $json['timetoken'], + ); + + if (isset($json['uuid'])) { + $item->setUuid($json['uuid']); + } + + if (isset($json['message_type'])) { + $item->setMessageType($json['message_type']); + } + + if (isset($json['meta'])) { + $item->setMetadata($json['meta']); + } + + if (isset($json['actions'])) { + $item->setActions($json['actions']); + } else { + $item->setActions([]); + } + + return $item; + } + + public function __toString(): string + { + return sprintf("Fetch message item with tt: %s and content: %s", $this->timetoken, $this->message); + } +} diff --git a/src/PubNub/Models/Consumer/MessagePersistence/PNFetchMessagesResult.php b/src/PubNub/Models/Consumer/MessagePersistence/PNFetchMessagesResult.php new file mode 100644 index 00000000..4e82d5af --- /dev/null +++ b/src/PubNub/Models/Consumer/MessagePersistence/PNFetchMessagesResult.php @@ -0,0 +1,62 @@ +channels = $channels; + $this->startTimetoken = $startTimetoken; + $this->endTimetoken = $endTimetoken; + } + + public function __toString() + { + return sprintf("Fetch messages result for range %d..%d", $this->startTimetoken, $this->endTimetoken); + } + + public static function fromJson($jsonInput, $crypto, $startTimetoken, $endTimetoken) + { + $channels = []; + + foreach ($jsonInput['channels'] as $channel => $messages) { + foreach ($messages as $item) { + $channels[$channel][] = PNFetchMessagesItemResult::fromJson($item, $crypto); + } + } + return new static($channels, $startTimetoken, $endTimetoken); + } + + public function getChannels() + { + return $this->channels; + } + + /** + * @return int + */ + public function getStartTimetoken() + { + return $this->startTimetoken; + } + + /** + * @return int + */ + public function getEndTimetoken() + { + return $this->endTimetoken; + } +} diff --git a/src/PubNub/PubNub.php b/src/PubNub/PubNub.php index 419fa7fa..72c6cd6f 100644 --- a/src/PubNub/PubNub.php +++ b/src/PubNub/PubNub.php @@ -17,6 +17,7 @@ use PubNub\Endpoints\History; use PubNub\Endpoints\HistoryDelete; use PubNub\Endpoints\MessageCount; +use PubNub\Endpoints\MessagePersistance\FetchMessages; use PubNub\Endpoints\Objects\Channel\SetChannelMetadata; use PubNub\Endpoints\Objects\Channel\GetChannelMetadata; use PubNub\Endpoints\Objects\Channel\GetAllChannelMetadata; @@ -560,12 +561,12 @@ public function setToken($token) return $this->tokenManager->setToken($token); } - public function getCrypto(): CryptoModule + public function getCrypto(): CryptoModule | null { if ($this->cryptoModule) { return $this->cryptoModule; } else { - return $this->configuration->getCrypto(); + return $this->configuration->getCryptoSafe(); } } @@ -573,4 +574,9 @@ public function setCrypto(CryptoModule $cryptoModule) { $this->cryptoModule = $cryptoModule; } + + public function fetchMessages(): FetchMessages + { + return new FetchMessages($this); + } } diff --git a/tests/helpers/Stub.php b/tests/helpers/Stub.php index bdf6f859..31dad6b2 100644 --- a/tests/helpers/Stub.php +++ b/tests/helpers/Stub.php @@ -2,7 +2,6 @@ namespace Tests\Helpers; - class Stub { const ANY = 'any value'; @@ -116,9 +115,16 @@ public function withQuery($query) return $this; } + private function stripKeys($path) + { + $patterns = ['/sub-key\/(demo|sub-c-[a-z0-9-]{36})\//']; + $replaces = ["sub-key/{SUB_KEY}/"]; + return preg_replace($patterns, $replaces, $path); + } + public function isPathMatch($path) { - return $this->path === $path; + return $this->stripKeys($this->path) === $this->stripKeys($path); } public function isQueryMatch($actualQueryString) @@ -170,6 +176,7 @@ public function queryString() } } +// phpcs:ignore PSR1.Classes.ClassDeclaration class StubException extends \Exception { } diff --git a/tests/integrational/FetchMessagesTest.php b/tests/integrational/FetchMessagesTest.php new file mode 100644 index 00000000..0d92c0af --- /dev/null +++ b/tests/integrational/FetchMessagesTest.php @@ -0,0 +1,169 @@ +pubnub); + + $fetchMessages + ->stubFor("/v3/history/sub-key/demo/channel/TheMessageHistoryChannelHD") + ->withQuery([ + "uuid" => $this->pubnub->getConfiguration()->getUserId(), + "pnsdk" => $this->encodedSdkName + ]) + ->setResponseBody('{"status": 200, "error": false, "error_message": "", "channels": + {"TheMessageHistoryChannelHD":[ + {"message":"hello TheMessageHistoryChannelHD channel. Message: 1","timetoken":"17165627034260904"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 2","timetoken":"17165627036256425"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 3","timetoken":"17165627038256616"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 4","timetoken":"17165627040258555"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 5","timetoken":"17165627042258446"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 6","timetoken":"17165627044259064"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 7","timetoken":"17165627046254982"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 8","timetoken":"17165627048260069"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 9","timetoken":"17165627050260263"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 10","timetoken":"17165627052255699"} + ]}}'); + + $response = $fetchMessages->channels(self::CHANNEL_NAME)->sync(); + $this->assertInstanceOf(PNFetchMessagesResult::class, $response); + + $this->assertEquals( + self::MESSAGE_COUNT, + count($response->getChannels()[self::CHANNEL_NAME]) + ); + } + + public function testFetchWithCount() + { + $fetchMessages = new FetchMessagesExposed($this->pubnub); + + $fetchMessages + ->stubFor("/v3/history/sub-key/demo/channel/TheMessageHistoryChannelHD") + ->withQuery([ + "max" => "5", + "uuid" => $this->pubnub->getConfiguration()->getUserId(), + "pnsdk" => $this->encodedSdkName + ]) + ->setResponseBody('{"status": 200, "error": false, "error_message": "", "channels": + {"TheMessageHistoryChannelHD":[ + {"message":"hello TheMessageHistoryChannelHD channel. Message: 6","timetoken":"17165627044259064"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 7","timetoken":"17165627046254982"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 8","timetoken":"17165627048260069"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 9","timetoken":"17165627050260263"}, + {"message":"hello TheMessageHistoryChannelHD channel. Message: 10","timetoken":"17165627052255699"} + ]}}'); + + $response = $fetchMessages->channels(self::CHANNEL_NAME) + ->count(5) + ->sync(); + + $this->assertInstanceOf(PNFetchMessagesResult::class, $response); + + $this->assertEquals(5, count($response->getChannels()[self::CHANNEL_NAME])); + } + + public function testFetchWithStartEnd() + { + $fetchMessages = new FetchMessagesExposed($this->pubnub); + + $fetchMessages + ->stubFor("/v3/history/sub-key/demo/channel/TheMessageHistoryChannelHD") + ->withQuery([ + "start" => "17165627042258346", + "end" => "17165627042258546", + "uuid" => $this->pubnub->getConfiguration()->getUserId(), + "pnsdk" => $this->encodedSdkName + ]) + ->setResponseBody('{"status": 200, "error": false, "error_message": "", "channels": + {"TheMessageHistoryChannelHD":[ + {"message":"hello TheMessageHistoryChannelHD channel. Message: 5","timetoken":"17165627042258446"} + ]}}'); + + $response = $fetchMessages->channels(self::CHANNEL_NAME) + ->start(17165627042258346) + ->end(17165627042258546) + ->sync(); + + $this->assertInstanceOf(PNFetchMessagesResult::class, $response); + $this->assertEquals(1, count($response->getChannels()[self::CHANNEL_NAME])); + $this->assertEquals( + 'hello ' . self::CHANNEL_NAME . ' channel. Message: 5', + $response->getChannels()[self::CHANNEL_NAME][0]->getMessage() + ); + } + + public function testFetchEncrypted() + { + $fetchMessages = new FetchMessagesExposed($this->pubnub_enc); + + $fetchMessages + ->stubFor("/v3/history/sub-key/demo/channel/TheMessageHistoryChannelHD-ENCRYPTED") + ->withQuery([ + "uuid" => $this->pubnub->getConfiguration()->getUserId(), + "pnsdk" => $this->encodedSdkName + ]) + + ->setResponseBody('{"status": 200, "error": false, "error_message": "", "channels": { + "TheMessageHistoryChannelHD-ENCRYPTED":[ + {"message":"CRD1ctIrZLGyFa4qqQcQVfvSOWeSNkPdxCs9CEsA/eE3Et3mfZaTDV3ANv1l/pc/", + "timetoken":"17165627054255980" + } + ]}}'); + + $response = $fetchMessages->channels(self::ENCRYPTED_CHANNEL_NAME) + ->sync(); + + $this->assertInstanceOf(PNFetchMessagesResult::class, $response); + $this->assertEquals(1, count($response->getChannels()[self::ENCRYPTED_CHANNEL_NAME])); + + $this->assertEquals( + 'Hey. This one is a secret ;-)', + $response->getChannels()[self::ENCRYPTED_CHANNEL_NAME][0]->getMessage() + ); + } +} + +// phpcs:ignore PSR1.Classes.ClassDeclaration +class FetchMessagesExposed extends FetchMessages +{ + protected $transport; + + public function __construct(PubNub $pubnubInstance) + { + parent::__construct($pubnubInstance); + + $this->transport = new StubTransport(); + } + + public function stubFor($url) + { + return $this->transport->stubFor($url); + } + + public function requestOptions() + { + return [ + 'transport' => $this->transport + ]; + } +}