generated from spatie/package-skeleton-php
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Moved request helpers into trait. - Created HaWebhookClient. - Created HaWebhookClientTest. - Created WebhookSuccess response definition.
- Loading branch information
Showing
5 changed files
with
465 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace IndexZer0\HaRestApiClient; | ||
|
||
use Http\Client\Common\Plugin\BaseUriPlugin; | ||
use Http\Client\Common\Plugin\ErrorPlugin; | ||
use IndexZer0\HaRestApiClient\HttpClient\Builder; | ||
use IndexZer0\HaRestApiClient\Traits\HandlesRequests; | ||
|
||
class HaWebhookClient | ||
{ | ||
use HandlesRequests; | ||
|
||
private array $supportedPayloadTypes = [ | ||
'json', | ||
'form_params', | ||
]; | ||
|
||
private array $supportedHttpMethods = [ | ||
'GET', | ||
'HEAD', | ||
'PUT', | ||
'POST' | ||
]; | ||
|
||
public function __construct( | ||
private string $baseUri, | ||
public readonly Builder $httpClientBuilder = new Builder(), | ||
) { | ||
$this->httpClientBuilder->addPlugin(new BaseUriPlugin( | ||
$this->httpClientBuilder->getUriFactory()->createUri($this->baseUri), | ||
[ | ||
// Always replace the host, even if this one is provided on the sent request. Available for AddHostPlugin. | ||
'replace' => true, | ||
] | ||
)); | ||
$this->httpClientBuilder->addPlugin(new ErrorPlugin()); | ||
} | ||
|
||
/* | ||
* https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger | ||
* Webhooks support HTTP POST, PUT, HEAD, and GET requests. | ||
* | ||
* Note that a given webhook can only be used in one automation at a time. That is, only one automation trigger can use a specific webhook ID. | ||
* | ||
* https://www.home-assistant.io/docs/automation/trigger/#webhook-data | ||
* | ||
* Payloads may either be encoded as form data or JSON. | ||
* Depending on that, its data will be available in an automation template as either trigger.data or trigger.json | ||
* URL query parameters are also available in the template as trigger.query. | ||
* | ||
* Note that to use JSON encoded payloads, the Content-Type header must be set to application/json | ||
* | ||
* https://www.home-assistant.io/docs/automation/trigger/#webhook-security | ||
* | ||
* ---------------------------------------------------------------------------- | ||
* | ||
* See templating for webhooks in automations. | ||
* https://www.home-assistant.io/docs/automation/templating/#all | ||
* https://www.home-assistant.io/docs/automation/templating/#webhook | ||
*/ | ||
public function send( | ||
string $method, | ||
string $webhookId, | ||
?array $queryParams = null, | ||
?string $payloadType = null, | ||
?array $data = null, | ||
): array { | ||
if (!in_array($method, $this->supportedHttpMethods, true)) { | ||
throw new HaException("\$method must be one of: " . join(', ', $this->supportedHttpMethods)); | ||
} | ||
|
||
$request = $this->createRequestWithQuery($method, "/webhook/{$webhookId}", $queryParams ?? []); | ||
|
||
if ($payloadType !== null) { | ||
if (!in_array($payloadType, $this->supportedPayloadTypes, true)) { | ||
throw new HaException("\$payloadType must be one of: " . join(', ', $this->supportedPayloadTypes)); | ||
} | ||
|
||
if ($data === null) { | ||
throw new HaException("\$data must be provided when providing \$payloadType"); | ||
} | ||
|
||
$request = $request->withHeader('Content-Type', $payloadType === 'json' ? 'application/json' : 'application/x-www-form-urlencoded'); | ||
|
||
$request = $request->withBody( | ||
$this->httpClientBuilder->getStreamFactory()->createStream( | ||
$payloadType === 'json' ? json_encode($data) : http_build_query($data, '', '&') | ||
) | ||
); | ||
} | ||
|
||
return $this->handleRequest( | ||
$request | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace IndexZer0\HaRestApiClient\Traits; | ||
|
||
use Http\Client\Common\Exception\ClientErrorException; | ||
use IndexZer0\HaRestApiClient\HaException; | ||
use JsonException; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Throwable; | ||
|
||
trait HandlesRequests | ||
{ | ||
/* | ||
* Send request and handle responses. | ||
*/ | ||
private function handleRequest(RequestInterface $request): array | ||
{ | ||
try { | ||
$response = $this->httpClientBuilder->getHttpClient()->sendRequest($request); | ||
} catch (ClientErrorException $ce) { | ||
throw new HaException($ce->getResponse()->getBody()->getContents(), previous: $ce); | ||
} catch (Throwable $t) { | ||
throw new HaException('Unknown Error.', previous: $t); | ||
} | ||
|
||
$responseBodyContent = $response->getBody()->getContents(); | ||
|
||
$responseContentType = $this->getContentTypeFromResponse($response) ?? 'application/json'; | ||
|
||
if ($responseContentType === 'application/json') { | ||
try { | ||
$json = json_decode($responseBodyContent, true, flags: JSON_THROW_ON_ERROR); | ||
|
||
// This is a failsafe for if the home assistant json response is not an array when decoded | ||
// For example if $responseBodyContent = 'null'; | ||
// Not seen this scenario in the wild but handling this json decode case anyway. | ||
if (!is_array($json)) { | ||
return [$json]; | ||
} | ||
|
||
return $json; | ||
} catch (JsonException $je) { | ||
// This should never happen. | ||
// If it does, it means home assistant is returning invalid json with application/json Content-Type header. | ||
throw new HaException('Invalid JSON Response.', previous: $je); | ||
} | ||
} | ||
|
||
// Some responses come back with Content-Type header of text/plain. | ||
// Such as errorLog and renderTemplate. | ||
// So lets just wrap in an array to satisfy return type and keep api consistent. | ||
return [ | ||
'response' => $responseBodyContent | ||
]; | ||
} | ||
|
||
private function getContentTypeFromResponse(ResponseInterface $response): ?string | ||
{ | ||
return $response->hasHeader('Content-Type') ? $response->getHeader('Content-Type')[0] : null; | ||
} | ||
|
||
private function createRequestWithQuery(string $method, $uri, array $query): RequestInterface | ||
{ | ||
$request = $this->httpClientBuilder->getRequestFactory()->createRequest($method, $uri); | ||
return $request->withUri( | ||
$request->getUri()->withQuery(http_build_query($query)) | ||
); | ||
} | ||
} |
Oops, something went wrong.