Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
vendor

examples/archive.tar
*.php

composer.lock
.DS_Store
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ This releases brings support for the `CID` IPFS identifiers.
#### Updates

- Enhanced the `IPFSClient::ping` unit test to handle the actual response format from IPFS nodes.

## v1.2.0

This release brings support for the `swarm/peers` command.

#### Additions

- Added the `IPFSClient::getPeers` method, which returns a list of all the node's connected peers.
- Added `Peer`, `PeerIdentity`, `PeerStream` models and corresponding transformers
- Added corresponding tests for the new transformers and methods.
34 changes: 34 additions & 0 deletions src/Client/IPFSClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@

use IPFS\Exception\IPFSTransportException;
use IPFS\Model\File;
use IPFS\Model\ListFileEntry;
use IPFS\Model\Node;
use IPFS\Model\Peer;
use IPFS\Model\Ping;
use IPFS\Model\Version;
use IPFS\Transformer\FileLinkTransformer;
use IPFS\Transformer\FileListTransformer;
use IPFS\Transformer\FileTransformer;
use IPFS\Transformer\NodeTransformer;
use IPFS\Transformer\PeerIdentityTransformer;
use IPFS\Transformer\PeerStreamTransformer;
use IPFS\Transformer\PeerTransformer;
use IPFS\Transformer\PingTransformer;
use IPFS\Transformer\VersionTransformer;

Expand Down Expand Up @@ -81,6 +86,9 @@ public function get(string $hash, bool $archive = false, bool $compress = false,
]);
}

/**
* @return ListFileEntry[]
*/
public function list(string $hash): array
{
$response = $this->httpClient->request('POST', '/api/v0/ls', [
Expand Down Expand Up @@ -110,6 +118,9 @@ public function getNode(?string $nodeId = null): Node
return $nodeTransformer->transform($parsedResponse);
}

/**
* @return string[]
*/
public function pin(string $path, bool $recursive = false, ?string $name = null): array
{
$response = $this->httpClient->request('POST', '/api/v0/pin/add', [
Expand All @@ -125,6 +136,9 @@ public function pin(string $path, bool $recursive = false, ?string $name = null)
return $parsedResponse['Pins'] ?? [];
}

/**
* @return string[]
*/
public function unpin(string $path, bool $recursive = false): array
{
$response = $this->httpClient->request('POST', '/api/v0/pin/rm', [
Expand Down Expand Up @@ -185,4 +199,24 @@ public function getConfiguration(): array

return json_decode($response, true);
}

/**
* @return Peer[]
*/
public function getPeers(bool $verbose = false): array
{
$response = $this->httpClient->request('POST', '/api/v0/swarm/peers', [
'query' => [
'verbose' => $verbose,
],
]);

$parsedResponse = json_decode($response, true);

$peerTransformer = new PeerTransformer(
peerIdentityTransformer: new PeerIdentityTransformer(),
peerStreamTransformer: new PeerStreamTransformer(),
);
return $peerTransformer->transformList($parsedResponse['Peers'] ?? []);
}
}
22 changes: 22 additions & 0 deletions src/Model/Peer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace IPFS\Model;

readonly class Peer
{
/**
* @param PeerStream[] $streams
*/
public function __construct(
public string $address,
public string $identifier,
public ?int $direction = null,
public ?PeerIdentity $identity = null,
public ?float $latency = null,
public ?string $muxer = null,
public array $streams = [],
) {
}
}
17 changes: 17 additions & 0 deletions src/Model/PeerIdentity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace IPFS\Model;

readonly class PeerIdentity
{
public function __construct(
public string $id,
public string $publicKey,
public ?string $agentVersion = null,
public array $addresses = [],
public array $protocols = [],
) {
}
}
13 changes: 13 additions & 0 deletions src/Model/PeerStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace IPFS\Model;

readonly class PeerStream
{
public function __construct(
public string $protocol,
) {
}
}
6 changes: 5 additions & 1 deletion src/Transformer/AbstractTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@

abstract class AbstractTransformer
{
protected function assertParameters(array $array = [], array $mandatoryParameters = []): void
protected function assertParameters(array $array = [], array $mandatoryParameters = [], bool $nonEmpty = false): void
{
foreach ($mandatoryParameters as $parameter) {
if (isset($array[$parameter]) === false) {
throw new \InvalidArgumentException(\sprintf('Parameter %s is missing', $parameter));
}

if ($nonEmpty === true && empty($array[$parameter]) === true) {
throw new \InvalidArgumentException(\sprintf('Parameter %s should not be empty', $parameter));
}
}
}

Expand Down
23 changes: 23 additions & 0 deletions src/Transformer/PeerIdentityTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace IPFS\Transformer;

use IPFS\Model\PeerIdentity;

class PeerIdentityTransformer extends AbstractTransformer
{
public function transform(array $input): PeerIdentity
{
$this->assertParameters($input, ['ID', 'PublicKey'], true);

return new PeerIdentity(
id: (string) $input['ID'],
publicKey: (string) $input['PublicKey'],
agentVersion: isset($input['AgentVersion']) ? (string) $input['AgentVersion'] : null,
addresses: $input['Addresses'] ?? [],
protocols: $input['Protocols'] ?? [],
);
}
}
22 changes: 22 additions & 0 deletions src/Transformer/PeerStreamTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace IPFS\Transformer;

use IPFS\Model\PeerStream;

/**
* @method PeerStream[] transformList(array $data)
*/
class PeerStreamTransformer extends AbstractTransformer
{
public function transform(array $input): PeerStream
{
$this->assertParameters($input, ['Protocol']);

return new PeerStream(
protocol: (string) $input['Protocol'],
);
}
}
42 changes: 42 additions & 0 deletions src/Transformer/PeerTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace IPFS\Transformer;

use IPFS\Model\Peer;

/**
* @method Peer[] transformList(array $data)
*/
class PeerTransformer extends AbstractTransformer
{
public function __construct(
private readonly PeerIdentityTransformer $peerIdentityTransformer,
private readonly PeerStreamTransformer $peerStreamTransformer,
) {
}

public function transform(array $input): Peer
{
$this->assertParameters($input, ['Addr', 'Peer']);

$peerIdentity = null;

try {
$peerIdentity = $this->peerIdentityTransformer->transform($input['Identify']);
} catch (\Throwable) {
// Ignore
}

return new Peer(
address: (string) $input['Addr'],
identifier: (string) $input['Peer'],
direction: isset($input['Direction']) ? (int) $input['Direction'] : null,
identity: $peerIdentity,
latency: isset($input['Latency']) ? (float) $input['Latency'] : null,
muxer: isset($input['Muxer']) ? (string) $input['Muxer'] : null,
streams: $this->peerStreamTransformer->transformList($input['Streams'] ?? []),
);
}
}
94 changes: 94 additions & 0 deletions tests/Client/IPFSClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use IPFS\Client\IPFSClient;
use IPFS\Client\ScopedHttpClient;
use IPFS\Model\Peer;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -346,4 +347,97 @@ public function testGetConfiguration(): void

$this->assertSame($mockReturn, $result);
}

/**
* @covers ::getPeers
*/
public function testGetPeers(): void
{
$mockReturn = [
'Peers' => [
[
'Addr' => '/ip4/139.178.65.157/udp/4001/quic-v1',
'Peer' => 'QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',
'Latency' => '130.549486ms',
'Direction' => 2,
'Streams' => [
[
'Protocol' => '/ipfs/kad/1.0.0',
],
],
'Identify' => [
'ID' => 'QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',
'PublicKey' => 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwin2xA7JpMY/vKGHjjupGH7AhJ451wnfhPqG4glnIKFz41NDZa/bQXk05Gw/SeUONsUUbQ5qiBR1WZR4ExzZaipuRqGkPDdgoG2b1gVtFeUharsE5mLNQP6M2mjOGXpLH/tVP8ONe4FkqKLnt9EJ1sIjRr/qxs+uCxheHepCMmzzCnpIwwOqDBhmEQDWDmX4QsosPCdco2TDzLvSJiCXhuMZ6k8MZgt9EfMjpxri7euDgBnw4JFmWFpyfJlDose5z8F84bKd5DBgWdhFObiJUyI9IEv1j7lMobHYJtu9WVLhgkLUYUnt05qLqysPpZHlnmahi8plolCByNeEvPkubAgMBAAE=',
'Addresses' => [
'/dns4/ny5.bootstrap.libp2p.io/tcp/443/wss/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',
],
'AgentVersion' => 'rust-libp1p-server/0.12.3',
'Protocols' => [
'/ipfs/id/1.0.0',
],
],
],
[
'Addr' => '/ip4/139.178.65.157/udp/4001/quic-v1',
'Peer' => 'QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
'Latency' => '130.549486ms',
'Direction' => 2,
'Streams' => [
[
'Protocol' => '/ipfs/dahk/1.0.0',
],
],
'Identify' => [
'ID' => 'QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
'PublicKey' => 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwin2xA7JpMY/vKGHjjupGH7AhJ451wnfhPqG4glnIKFz41NDZa/bQXk05Gw/SeUONsUUbQ5qiBR1WZR4ExzZaipuRqGkPDdgoG2b1gVtFeUharsE5mLNQP6M2mjOGXpLH/tVP8ONe4FkqKLnt9EJ1sIjRr/qxs+uCxheHepCMmzzCnpIwwOqDBhmEQDWDmX4QsosPCdco2TDzLvSJiCXhuMZ6k8MZgt9EfMjpxri7euDgBnw4JFmWFpyfJlDose5z8F84bKd5DBgWdhFObiJUyI9IEv1j7lMobHYJtu9WVLhgkLUYUnt05qLqysPpZHlnmahi8plolCByNeEvPkubAgMBAAE=',
'Addresses' => [
'/dns4/ny5.bootstrap.libp2p.io/tcp/443/wss/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
],
'AgentVersion' => 'rust-libp2p-server/0.12.3',
'Protocols' => [
'/ipfs/id/2.0.0',
],
],
],
],
];

$this->httpClient
->expects($this->once())
->method('request')
->with('POST', '/api/v0/swarm/peers')
->willReturn(json_encode($mockReturn));

$result = $this->client->getPeers();

$this->assertCount(\count($mockReturn['Peers']), $result);

foreach ($result as $key => $peer) {
$correspondingMockData = $mockReturn['Peers'][$key];
$this->assertPeerResponse($correspondingMockData, $peer);
}
}

private function assertPeerResponse(array $data, Peer $peer): void
{
$this->assertSame($data['Addr'], $peer->address);
$this->assertSame($data['Peer'], $peer->identifier);
$this->assertSame($data['Direction'], $peer->direction);
$this->assertSame((float) $data['Latency'], $peer->latency);

foreach ($peer->streams as $key => $stream) {
$correspondingResponseStream = $data['Streams'][$key];

$this->assertSame($stream->protocol, $correspondingResponseStream['Protocol']);
}

$identity = $peer->identity;
$this->assertNotNull($identity);

$this->assertSame($data['Identify']['ID'], $identity->id);
$this->assertSame($data['Identify']['AgentVersion'], $identity->agentVersion);
$this->assertSame($data['Identify']['PublicKey'], $identity->publicKey);
$this->assertSame($data['Identify']['Addresses'], $identity->addresses);
$this->assertSame($data['Identify']['Protocols'], $identity->protocols);
}
}
Loading