Skip to content

Commit

Permalink
Merge pull request #60 from nostrver-se/kriptonix-relay-responses-han…
Browse files Browse the repository at this point in the history
…dling

Improve relay responses handling
  • Loading branch information
Sebastix authored Aug 29, 2024
2 parents d808086 + 1d36b3a commit d5a440a
Show file tree
Hide file tree
Showing 19 changed files with 357 additions and 59 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,18 +264,18 @@ private key on command line.
- [x] Convert from hex to bech32-encoded keys
- [x] Event signing with Schnorr signatures (`secp256k1`)
- [x] Event validation (issue [#17](https://github.com/nostrver-se/nostr-php/issues/17))
- [ ] Support NIP-01 basic protocol flow description
- [x] Support NIP-01 basic protocol flow description
- [x] Publish events
- [x] Request events (issue [#55](https://github.com/nostrver-se/nostr-php/pull/55) credits to [kriptonix](https://github.com/kriptonix))
- [ ] Implement all types of relay responses
- [ ] EVENT - sends events requested by the client
- [ ] OK - indicate an acceptance or denial of an EVENT message
- [ ] EOSE - end of stored events
- [ ] CLOSED - subscription is ended on the server side
- [ ] NOTICE - used to send human-readable messages (like errors) to clients
- [ ] Improve handling relay responses
- [x] Implement all types of relay responses
- [x] `EVENT` - sends events requested by the client
- [x] `OK` - indicate an acceptance or denial of an EVENT message
- [x] `EOSE` - end of stored events
- [x] `CLOSED` - subscription is ended on the server side
- [x] `NOTICE` - used to send human-readable messages (like errors) to clients
- [x] Improve handling relay responses
- [ ] Support NIP-19 bech32-encoded identifiers
- [ ] Support NIP-42 authentication of clients to relays
- [ ] Support NIP-42 authentication of clients to relays => AUTH relay response
- [ ] Support NIP-45 event counts
- [ ] Support NIP-50 search capability
- [ ] Support multi-threading (async concurrency) for handling requests simultaneously
Expand All @@ -296,3 +296,4 @@ In May 2024 OpenSats granted Sebastian Hagens for further development of this li

## Contributors

See https://github.com/nostrver-se/nostr-php/graphs/contributors
29 changes: 21 additions & 8 deletions src/Relay/Relay.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
use swentel\nostr\MessageInterface;
use swentel\nostr\RelayInterface;
use swentel\nostr\CommandResultInterface;
use swentel\nostr\RelayResponse\RelayResponseOk;
use swentel\nostr\RelayResponse\RelayResponseNotice;
use swentel\nostr\RelayResponse\RelayResponse;
use swentel\nostr\RelayResponseInterface;
use WebSocket;

class Relay implements RelayInterface
Expand Down Expand Up @@ -47,12 +51,19 @@ public function __construct(string $websocket, MessageInterface $message = null)
}
}

private function validateUrl(): void
{
if (!preg_match('/^(ws|wss):\/\//', $this->url)) {
throw new \InvalidArgumentException('Invalid URL format. URL must start with ws:// or wss://');
}
}

/**
* {@inheritdoc}
*/
public function setUrl(string $url): void
{
// TODO validate this URL which has to start with a prefix ws:// or wss://.
$this->validateUrl();
$this->url = $url;
}

Expand Down Expand Up @@ -82,27 +93,29 @@ private function setPayload(string $payload): void
}

/**
* {@inheritdoc}
* @inheritDoc
*/
public function send(): CommandResultInterface
public function send(): RelayResponse
{
$this->validateUrl();

try {
$client = new WebSocket\Client($this->url);
$client->text($this->payload);
$response = $client->receive();
$client->disconnect();
$response = json_decode($response->getContent());
if ($response[0] === 'NOTICE') {
throw new \RuntimeException($response[1]);
if ($response === null) {
throw new \RuntimeException('Websocket client response is null');
}
$result = RelayResponse::create(json_decode($response->getContent()));
} catch (WebSocket\Exception\ClientException $e) {
$response = [
$result = [
'ERROR',
'',
false,
$e->getMessage(),
];
}
return new CommandResult($response);
return $result;
}
}
17 changes: 10 additions & 7 deletions src/Relay/RelaySet.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace swentel\nostr\Relay;

use swentel\nostr\CommandResultInterface;
use swentel\nostr\MessageInterface;
use swentel\nostr\RelayResponse\RelayResponse;
use swentel\nostr\RelaySetInterface;
use WebSocket;

Expand Down Expand Up @@ -112,7 +112,7 @@ public function isConnected(): bool
/**
* @inheritDoc
*/
public function send(): CommandResultInterface
public function send(): array
{
try {
// Send message to each relay defined in this set.
Expand All @@ -123,19 +123,22 @@ public function send(): CommandResultInterface
$client->text($payload);
$response = $client->receive();
$client->disconnect();
$response = json_decode($response->getContent());
if ($response[0] === 'NOTICE') {
throw new \RuntimeException($response[1]);
if ($response->getOpcode() === 'ping') {
continue;
}
if ($response === null) {
throw new \RuntimeException('Websocket client response is null');
}
$result[$relay->getUrl()] = RelayResponse::create(json_decode($response->getContent()));
}
} catch (WebSocket\Exception\ClientException $e) {
$response = [
$result = [
'ERROR',
'',
false,
$e->getMessage(),
];
}
return new CommandResult($response);
return $result;
}
}
4 changes: 2 additions & 2 deletions src/RelayInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function setMessage(MessageInterface $message): void;
/**
* Sends the message to the relay.
*
* @return CommandResultInterface
* @return RelayResponseInterface
*/
public function send(): CommandResultInterface;
public function send(): RelayResponseInterface;
}
82 changes: 82 additions & 0 deletions src/RelayResponse/RelayResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

use swentel\nostr\RelayResponseInterface;

class RelayResponse implements RelayResponseInterface
{
public string $type;

public bool $isSuccess;

public string $message;

public function __construct(array $response)
{
$this->isSuccess = false;
if (isset($response[0])) {
$this->isSuccess = true;
$this->type = RelayResponseEnum::from($response[0])->value;
// Piece of legacy to support version <=1.3.3
if ($this->type === 'OK' && $response[2] === false) {
$this->isSuccess = false;
}
$this->message = !empty($response[3]) ? $response[3] : '';
}
}

/**
* Create a response object based on the given type using a match expression.
*
* @param array $response The response data to be used for creating the object.
* @param string $type The type of response to determine which object to create.
* @return object The created response object based on the type.
*/
public static function createResponse(array $response, string $type): mixed
{
return match ($type) {
'ERROR', 'NOTICE' => new RelayResponseNotice($response),
'EVENT' => new RelayResponseEvent($response),
'OK' => new RelayResponseOk($response),
'EOSE' => new RelayResponseEose($response),
'CLOSED' => new RelayResponseClosed($response),
'AUTH' => new RelayResponseAuth($response),
default => new self($response),
};
}

/**
* Static method to create a response object based on the given type using a match expression.
*
* @param array $response The response data to be used for creating the object.
* @return object The created response object based on the determined type.
*/
public static function create(array $response): mixed
{
$type = RelayResponseEnum::from($response[0])->value;
return self::createResponse($response, $type);
}

/**
* Backwards compatability support for <=1.3.3 where we used the CommandResultInterface as a response.
*
* @return bool
*/
public function isSuccess(): bool
{
return $this->isSuccess;
}

/**
* Backwards compatability support for <=1.3.3 where we used the CommandResultInterface as a response.
*
* @return string
*/
public function message(): string
{
return $this->message;
}
}
16 changes: 16 additions & 0 deletions src/RelayResponse/RelayResponseAuth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

class RelayResponseAuth extends RelayResponse
{
public string $message;

public function __construct($response)
{
parent::__construct($response);
$this->message = $response[1];
}
}
19 changes: 19 additions & 0 deletions src/RelayResponse/RelayResponseClosed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

class RelayResponseClosed extends RelayResponse
{
public string $subscriptionId;

public string $message;

public function __construct($response)
{
parent::__construct($response);
$this->subscriptionId = $response[1];
$this->message = $response[2];
}
}
22 changes: 22 additions & 0 deletions src/RelayResponse/RelayResponseEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

/**
* Enum with response types.
*/
enum RelayResponseEnum: string
{
case EVENT = 'EVENT';
case OK = 'OK';
case EOSE = 'EOSE';
case CLOSED = 'CLOSED';
case NOTICE = 'NOTICE';
/**
* NIP-42 support - Authentication of clients to relays
* https://github.com/nostr-protocol/nips/blob/master/42.md
*/
case AUTH = 'AUTH';
}
16 changes: 16 additions & 0 deletions src/RelayResponse/RelayResponseEose.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

class RelayResponseEose extends RelayResponse
{
public string $subscriptionId;

public function __construct($response)
{
parent::__construct($response);
$this->subscriptionId = $response[1];
}
}
19 changes: 19 additions & 0 deletions src/RelayResponse/RelayResponseEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

class RelayResponseEvent extends RelayResponse
{
public string $subscriptionId;

public \stdClass $event;

public function __construct($response)
{
parent::__construct($response);
$this->subscriptionId = $response[1];
$this->event = $response[2];
}
}
16 changes: 16 additions & 0 deletions src/RelayResponse/RelayResponseNotice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

class RelayResponseNotice extends RelayResponse
{
public string $message;

public function __construct($response)
{
parent::__construct($response);
$this->message = $response[1];
}
}
22 changes: 22 additions & 0 deletions src/RelayResponse/RelayResponseOk.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace swentel\nostr\RelayResponse;

class RelayResponseOk extends RelayResponse
{
public string $eventId;

public bool $status;

public string $message;

public function __construct($response)
{
parent::__construct($response);
$this->eventId = $response[1];
$this->status = $response[2];
$this->message = $response[3];
}
}
Loading

0 comments on commit d5a440a

Please sign in to comment.