Vendor
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\AuthorizesSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler;
|
||||
|
||||
class Authorizer implements AuthorizesSubscriptions
|
||||
{
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\SubscriptionRegistry
|
||||
*/
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler
|
||||
*/
|
||||
protected $exceptionHandler;
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions $storage
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\SubscriptionRegistry $registry
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler $exceptionHandler
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
StoresSubscriptions $storage,
|
||||
SubscriptionRegistry $registry,
|
||||
SubscriptionExceptionHandler $exceptionHandler
|
||||
) {
|
||||
$this->storage = $storage;
|
||||
$this->registry = $registry;
|
||||
$this->exceptionHandler = $exceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(Request $request): bool
|
||||
{
|
||||
try {
|
||||
$subscriber = $this->storage->subscriberByRequest(
|
||||
$request->input(),
|
||||
$request->headers->all()
|
||||
);
|
||||
|
||||
if (! $subscriber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscriptions = $this->registry->subscriptions($subscriber);
|
||||
|
||||
if ($subscriptions->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$authorized = $subscriptions->reduce(
|
||||
function ($authorized, GraphQLSubscription $subscription) use ($subscriber, $request): bool {
|
||||
return $authorized === false
|
||||
? false
|
||||
: $subscription->authorize($subscriber, $request);
|
||||
}
|
||||
);
|
||||
|
||||
if (! $authorized) {
|
||||
$this->storage->deleteSubscriber($subscriber->channel);
|
||||
}
|
||||
|
||||
return $authorized;
|
||||
} catch (Exception $e) {
|
||||
$this->exceptionHandler->handleAuthError($e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Pusher\Pusher;
|
||||
use RuntimeException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Nuwave\Lighthouse\Support\DriverManager;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Broadcasters\LogBroadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Broadcasters\PusherBroadcaster;
|
||||
|
||||
/**
|
||||
* @method void broadcast(\Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber, array $data)
|
||||
* @method \Symfony\Component\HttpFoundation\Response hook(\Illuminate\Http\Request $request)
|
||||
* @method \Symfony\Component\HttpFoundation\Response authorized(\Illuminate\Http\Request $request)
|
||||
* @method \Symfony\Component\HttpFoundation\Response unauthorized(\Illuminate\Http\Request $request)
|
||||
*/
|
||||
class BroadcastManager extends DriverManager
|
||||
{
|
||||
/**
|
||||
* Get configuration key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function configKey(): string
|
||||
{
|
||||
return 'lighthouse.subscriptions.broadcasters';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration driver key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function driverKey(): string
|
||||
{
|
||||
return 'lighthouse.subscriptions.broadcaster';
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface the driver should implement.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function interface(): string
|
||||
{
|
||||
return Broadcaster::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of pusher driver.
|
||||
*
|
||||
* @param mixed[] $config
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Broadcasters\PusherBroadcaster
|
||||
* @throws \Pusher\PusherException
|
||||
*/
|
||||
protected function createPusherDriver(array $config): PusherBroadcaster
|
||||
{
|
||||
$connection = $config['connection'] ?? 'pusher';
|
||||
$driverConfig = config("broadcasting.connections.{$connection}");
|
||||
|
||||
if (empty($driverConfig) || $driverConfig['driver'] !== 'pusher') {
|
||||
throw new RuntimeException("Could not initialize Pusher broadcast driver for connection: {$connection}.");
|
||||
}
|
||||
|
||||
$appKey = Arr::get($driverConfig, 'key');
|
||||
$appSecret = Arr::get($driverConfig, 'secret');
|
||||
$appId = Arr::get($driverConfig, 'app_id');
|
||||
$options = Arr::get($driverConfig, 'options', []);
|
||||
|
||||
$pusher = new Pusher($appKey, $appSecret, $appId, $options);
|
||||
|
||||
return new PusherBroadcaster($pusher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of log driver.
|
||||
*
|
||||
* @param mixed[] $config
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Broadcasters\LogBroadcaster
|
||||
*/
|
||||
protected function createLogDriver(array $config): LogBroadcaster
|
||||
{
|
||||
return new LogBroadcaster($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Broadcasters;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
|
||||
class LogBroadcaster implements Broadcaster
|
||||
{
|
||||
/**
|
||||
* The user-defined configuration options.
|
||||
*
|
||||
* @var mixed[]
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* A map from channel names to data.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $broadcasts = [];
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function authorized(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json(['message' => 'ok'], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unauthorized subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function unauthorized(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json(['error' => 'unauthorized'], 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle subscription web hook.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function hook(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json(['message' => 'okay']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to subscriber.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function broadcast(Subscriber $subscriber, array $data): void
|
||||
{
|
||||
$this->broadcasts[$subscriber->channel] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data that is being broadcast.
|
||||
*
|
||||
* @param string|null $key
|
||||
* @return array|null
|
||||
*/
|
||||
public function broadcasts(?string $key = null): ?array
|
||||
{
|
||||
return Arr::get($this->broadcasts, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration options.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function config(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Broadcasters;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
|
||||
class PusherBroadcaster implements Broadcaster
|
||||
{
|
||||
const EVENT_NAME = 'lighthouse-subscription';
|
||||
|
||||
/**
|
||||
* @var \Pusher\Pusher
|
||||
*/
|
||||
protected $pusher;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Create instance of pusher broadcaster.
|
||||
*
|
||||
* @param \Pusher\Pusher $pusher
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($pusher)
|
||||
{
|
||||
$this->pusher = $pusher;
|
||||
$this->storage = app(StoresSubscriptions::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function authorized(Request $request): JsonResponse
|
||||
{
|
||||
$channel = $request->input('channel_name');
|
||||
$socketId = $request->input('socket_id');
|
||||
$data = json_decode(
|
||||
$this->pusher->socket_auth($channel, $socketId),
|
||||
true
|
||||
);
|
||||
|
||||
return response()->json($data, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unauthorized subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function unauthorized(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json(['error' => 'unauthorized'], 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle subscription web hook.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function hook(Request $request): JsonResponse
|
||||
{
|
||||
(new Collection($request->input('events', [])))
|
||||
->filter(function ($event): bool {
|
||||
return Arr::get($event, 'name') === 'channel_vacated';
|
||||
})
|
||||
->each(function (array $event): void {
|
||||
$this->storage->deleteSubscriber(
|
||||
Arr::get($event, 'channel')
|
||||
);
|
||||
});
|
||||
|
||||
return response()->json(['message' => 'okay']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to subscriber.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param mixed[] $data
|
||||
* @return void
|
||||
*/
|
||||
public function broadcast(Subscriber $subscriber, array $data): void
|
||||
{
|
||||
$this->pusher->trigger(
|
||||
$subscriber->channel,
|
||||
self::EVENT_NAME,
|
||||
[
|
||||
'more' => true,
|
||||
'result' => $data,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Contracts;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
interface AuthorizesSubscriptions
|
||||
{
|
||||
/**
|
||||
* Authorize subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(Request $request);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Contracts;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
|
||||
interface Broadcaster
|
||||
{
|
||||
/**
|
||||
* Handle authorized subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function authorized(Request $request);
|
||||
|
||||
/**
|
||||
* Handle unauthorized subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function unauthorized(Request $request);
|
||||
|
||||
/**
|
||||
* Handle subscription web hook.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function hook(Request $request);
|
||||
|
||||
/**
|
||||
* Send data to subscriber.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param mixed[] $data
|
||||
* @return void
|
||||
*/
|
||||
public function broadcast(Subscriber $subscriber, array $data);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Contracts;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
|
||||
interface BroadcastsSubscriptions
|
||||
{
|
||||
/**
|
||||
* Push subscription data to subscribers.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription
|
||||
* @param string $fieldName
|
||||
* @param mixed $root
|
||||
*/
|
||||
public function broadcast(GraphQLSubscription $subscription, string $fieldName, $root);
|
||||
|
||||
/**
|
||||
* Queue pushing subscription data to subscribers.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription
|
||||
* @param string $fieldName
|
||||
* @param mixed $root
|
||||
* @return void
|
||||
*/
|
||||
public function queueBroadcast(GraphQLSubscription $subscription, string $fieldName, $root);
|
||||
|
||||
/**
|
||||
* Authorize the subscription.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function authorize(Request $request);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Contracts;
|
||||
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
|
||||
interface ContextSerializer
|
||||
{
|
||||
/**
|
||||
* Serialize the context.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(GraphQLContext $context);
|
||||
|
||||
/**
|
||||
* Unserialize the context.
|
||||
*
|
||||
* @param string $context
|
||||
* @return \Nuwave\Lighthouse\Support\Contracts\GraphQLContext
|
||||
*/
|
||||
public function unserialize(string $context);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Contracts;
|
||||
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
|
||||
interface StoresSubscriptions
|
||||
{
|
||||
/**
|
||||
* Get subscriber by request.
|
||||
*
|
||||
* @param array $input
|
||||
* @param array $headers
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Subscriber|null
|
||||
*/
|
||||
public function subscriberByRequest(array $input, array $headers);
|
||||
|
||||
/**
|
||||
* Find subscriber by channel.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Subscriber|null
|
||||
*/
|
||||
public function subscriberByChannel(string $channel);
|
||||
|
||||
/**
|
||||
* Get collection of subscribers by topic.
|
||||
*
|
||||
* @param string $topic
|
||||
* @return \Illuminate\Support\Collection<\Nuwave\Lighthouse\Subscriptions\Subscriber>
|
||||
*/
|
||||
public function subscribersByTopic(string $topic);
|
||||
|
||||
/**
|
||||
* Store subscription.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param string $topic
|
||||
* @return void
|
||||
*/
|
||||
public function storeSubscriber(Subscriber $subscriber, string $topic);
|
||||
|
||||
/**
|
||||
* Delete subscriber.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Subscriber|null
|
||||
*/
|
||||
public function deleteSubscriber(string $channel);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Contracts;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface SubscriptionExceptionHandler
|
||||
{
|
||||
/**
|
||||
* Handle authentication error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleAuthError(Throwable $e);
|
||||
|
||||
/**
|
||||
* Handle broadcast error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleBroadcastError(Throwable $e);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Contracts;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface SubscriptionIterator
|
||||
{
|
||||
/**
|
||||
* Process collection of items.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param \Closure $cb
|
||||
* @param \Closure|null $error
|
||||
* @return void
|
||||
*/
|
||||
public function process(Collection $items, Closure $cb, Closure $error = null);
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Events;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription as Subscription;
|
||||
|
||||
class BroadcastSubscriptionEvent
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription
|
||||
*/
|
||||
public $subscription;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $fieldName;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $root;
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription
|
||||
* @param string $fieldName
|
||||
* @param mixed $root
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Subscription $subscription, string $fieldName, $root)
|
||||
{
|
||||
$this->subscription = $subscription;
|
||||
$this->fieldName = $fieldName;
|
||||
$this->root = $root;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Events;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions;
|
||||
|
||||
class BroadcastSubscriptionListener implements ShouldQueue
|
||||
{
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions
|
||||
*/
|
||||
protected $broadcaster;
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions $broadcaster
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(BroadcastsSubscriptions $broadcaster)
|
||||
{
|
||||
$this->broadcaster = $broadcaster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Events\BroadcastSubscriptionEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(BroadcastSubscriptionEvent $event): void
|
||||
{
|
||||
$this->broadcaster->broadcast(
|
||||
$event->subscription,
|
||||
$event->fieldName,
|
||||
$event->root
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Throwable;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler;
|
||||
|
||||
class ExceptionHandler implements SubscriptionExceptionHandler
|
||||
{
|
||||
/**
|
||||
* Handle authentication error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleAuthError(Throwable $e): void
|
||||
{
|
||||
// Do nothing....
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle broadcast error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleBroadcastError(Throwable $e): void
|
||||
{
|
||||
info('graphql.broadcast.exception', [
|
||||
'message' => $e->getMessage(),
|
||||
'stack' => $e->getTrace(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Exceptions;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
|
||||
class UnauthorizedSubscriber extends Error
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Iterators;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator;
|
||||
|
||||
class SyncIterator implements SubscriptionIterator
|
||||
{
|
||||
/**
|
||||
* Process collection of items.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param \Closure $cb
|
||||
* @param \Closure|null $error
|
||||
* @return void
|
||||
*/
|
||||
public function process(Collection $items, Closure $cb, Closure $error = null): void
|
||||
{
|
||||
$items->each(function ($item) use ($cb, $error): void {
|
||||
try {
|
||||
$cb($item);
|
||||
} catch (Exception $e) {
|
||||
if (! $error) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$error($e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Support\Contracts\CreatesContext;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer;
|
||||
|
||||
class Serializer implements ContextSerializer
|
||||
{
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Support\Contracts\CreatesContext
|
||||
*/
|
||||
protected $createsContext;
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\Support\Contracts\CreatesContext $createsContext
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(CreatesContext $createsContext)
|
||||
{
|
||||
$this->createsContext = $createsContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the context.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(GraphQLContext $context): string
|
||||
{
|
||||
$request = $context->request();
|
||||
|
||||
return serialize([
|
||||
'request' => [
|
||||
'query' => $request->query->all(),
|
||||
'request' => $request->request->all(),
|
||||
'attributes' => $request->attributes->all(),
|
||||
'cookies' => [],
|
||||
'files' => [],
|
||||
'server' => Arr::except($request->server->all(), ['HTTP_AUTHORIZATION']),
|
||||
'content' => $request->getContent(),
|
||||
],
|
||||
'user' => serialize($context->user()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize the context.
|
||||
*
|
||||
* @param string $context
|
||||
* @return \Nuwave\Lighthouse\Support\Contracts\GraphQLContext
|
||||
*/
|
||||
public function unserialize(string $context): GraphQLContext
|
||||
{
|
||||
[
|
||||
'request' => $rawRequest,
|
||||
'user' => $rawUser
|
||||
] = unserialize($context);
|
||||
|
||||
$request = new Request(
|
||||
$rawRequest['query'],
|
||||
$rawRequest['request'],
|
||||
$rawRequest['attributes'],
|
||||
$rawRequest['cookies'],
|
||||
$rawRequest['files'],
|
||||
$rawRequest['server'],
|
||||
$rawRequest['content']
|
||||
);
|
||||
|
||||
$request->setUserResolver(
|
||||
function () use ($rawUser) {
|
||||
return unserialize($rawUser);
|
||||
}
|
||||
);
|
||||
|
||||
return $this->createsContext->generate($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Cache\CacheManager;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
|
||||
class StorageManager implements StoresSubscriptions
|
||||
{
|
||||
/**
|
||||
* The cache key for topics.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TOPIC_KEY = 'graphql.topic';
|
||||
|
||||
/**
|
||||
* The cache key for subscribers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SUBSCRIBER_KEY = 'graphql.subscriber';
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Cache\Repository
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Cache\CacheManager $cacheManager
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(CacheManager $cacheManager)
|
||||
{
|
||||
$this->cache = $cacheManager->store(
|
||||
config('lighthouse.subscriptions.storage', 'redis')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscriber by request.
|
||||
*
|
||||
* @param array $input
|
||||
* @param array $headers
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Subscriber|null
|
||||
*/
|
||||
public function subscriberByRequest(array $input, array $headers): ?Subscriber
|
||||
{
|
||||
$channel = Arr::get($input, 'channel_name');
|
||||
|
||||
return $channel
|
||||
? $this->subscriberByChannel($channel)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find subscriber by channel.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Subscriber|null
|
||||
*/
|
||||
public function subscriberByChannel(string $channel): ?Subscriber
|
||||
{
|
||||
$key = self::SUBSCRIBER_KEY.".{$channel}";
|
||||
|
||||
return $this->cache->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collection of subscribers by channel.
|
||||
*
|
||||
* @param string $topic
|
||||
* @return \Illuminate\Support\Collection<\Nuwave\Lighthouse\Subscriptions\Subscriber>
|
||||
*/
|
||||
public function subscribersByTopic(string $topic): Collection
|
||||
{
|
||||
$key = self::TOPIC_KEY.".{$topic}";
|
||||
|
||||
if (! $this->cache->has($key)) {
|
||||
return new Collection;
|
||||
}
|
||||
|
||||
$channels = json_decode($this->cache->get($key), true);
|
||||
|
||||
return (new Collection($channels))
|
||||
->map(function (string $channel): ?Subscriber {
|
||||
return $this->subscriberByChannel($channel);
|
||||
})
|
||||
->filter()
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store subscription.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param string $topic
|
||||
* @return void
|
||||
*/
|
||||
public function storeSubscriber(Subscriber $subscriber, string $topic): void
|
||||
{
|
||||
$topicKey = self::TOPIC_KEY.".{$topic}";
|
||||
$subscriberKey = self::SUBSCRIBER_KEY.".{$subscriber->channel}";
|
||||
|
||||
$topic = $this->cache->has($topicKey)
|
||||
? json_decode($this->cache->get($topicKey), true)
|
||||
: [];
|
||||
|
||||
$topic[] = $subscriber->channel;
|
||||
|
||||
$this->cache->forever($topicKey, json_encode($topic));
|
||||
$this->cache->forever($subscriberKey, $subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete subscriber.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Subscriber|null
|
||||
*/
|
||||
public function deleteSubscriber(string $channel): ?Subscriber
|
||||
{
|
||||
$key = self::SUBSCRIBER_KEY.".{$channel}";
|
||||
$hasSubscriber = $this->cache->has($key);
|
||||
|
||||
$subscriber = $this->cache->get($key);
|
||||
|
||||
if ($hasSubscriber) {
|
||||
$this->cache->forget($key);
|
||||
}
|
||||
|
||||
return $subscriber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Serializable;
|
||||
use GraphQL\Utils\AST;
|
||||
use Illuminate\Support\Str;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use Nuwave\Lighthouse\Exceptions\SubscriptionException;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer;
|
||||
|
||||
class Subscriber implements Serializable
|
||||
{
|
||||
const MISSING_OPERATION_NAME = 'Must pass an operation name when using a subscription.';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $channel;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $root;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var \GraphQL\Language\AST\DocumentNode
|
||||
*/
|
||||
public $query;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $operationName;
|
||||
|
||||
/**
|
||||
* @param mixed[] $args
|
||||
* @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context
|
||||
* @param \GraphQL\Type\Definition\ResolveInfo $resolveInfo
|
||||
* @return void
|
||||
*
|
||||
* @throws \Nuwave\Lighthouse\Exceptions\SubscriptionException
|
||||
*/
|
||||
public function __construct(
|
||||
array $args,
|
||||
GraphQLContext $context,
|
||||
ResolveInfo $resolveInfo
|
||||
) {
|
||||
$operationName = $resolveInfo->operation->name;
|
||||
if (! $operationName) {
|
||||
throw new SubscriptionException(
|
||||
self::MISSING_OPERATION_NAME
|
||||
);
|
||||
}
|
||||
$this->operationName = $operationName->value;
|
||||
|
||||
$this->channel = self::uniqueChannelName();
|
||||
$this->args = $args;
|
||||
$this->context = $context;
|
||||
|
||||
$documentNode = new DocumentNode([]);
|
||||
$documentNode->definitions = $resolveInfo->fragments;
|
||||
$documentNode->definitions[] = $resolveInfo->operation;
|
||||
$this->query = $documentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize subscription from a JSON string.
|
||||
*
|
||||
* @param string $subscription
|
||||
* @return $this
|
||||
*/
|
||||
public function unserialize($subscription): self
|
||||
{
|
||||
$data = json_decode($subscription, true);
|
||||
|
||||
$this->operationName = $data['operation_name'];
|
||||
$this->channel = $data['channel'];
|
||||
$this->args = $data['args'];
|
||||
$this->context = $this->contextSerializer()->unserialize(
|
||||
$data['context']
|
||||
);
|
||||
$this->query = AST::fromArray(
|
||||
unserialize($data['query'])
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this into a JSON string.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return json_encode([
|
||||
'operation_name' => $this->operationName,
|
||||
'channel' => $this->channel,
|
||||
'args' => $this->args,
|
||||
'context' => $this->contextSerializer()->serialize($this->context),
|
||||
'query' => serialize(
|
||||
AST::toArray($this->query)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set root data.
|
||||
*
|
||||
* @param mixed $root
|
||||
* @return $this
|
||||
*/
|
||||
public function setRoot($root): self
|
||||
{
|
||||
$this->root = $root;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique private channel name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function uniqueChannelName(): string
|
||||
{
|
||||
return 'private-lighthouse-'.Str::random(32).'-'.time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer
|
||||
*/
|
||||
protected function contextSerializer(): ContextSerializer
|
||||
{
|
||||
return app(ContextSerializer::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\GraphQL;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventsDispatcher;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\AuthorizesSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Events\BroadcastSubscriptionEvent;
|
||||
|
||||
class SubscriptionBroadcaster implements BroadcastsSubscriptions
|
||||
{
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\GraphQL
|
||||
*/
|
||||
protected $graphQL;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\AuthorizesSubscriptions
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\StorageManager
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator
|
||||
*/
|
||||
protected $iterator;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\BroadcastManager
|
||||
*/
|
||||
protected $broadcastManager;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
protected $eventsDispatcher;
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\GraphQL $graphQL
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Contracts\AuthorizesSubscriptions $auth
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\StorageManager $storage
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator $iterator
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\BroadcastManager $broadcastManager
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $eventsDispatcher
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
GraphQL $graphQL,
|
||||
AuthorizesSubscriptions $auth,
|
||||
StorageManager $storage,
|
||||
SubscriptionIterator $iterator,
|
||||
BroadcastManager $broadcastManager,
|
||||
EventsDispatcher $eventsDispatcher
|
||||
) {
|
||||
$this->graphQL = $graphQL;
|
||||
$this->auth = $auth;
|
||||
$this->storage = $storage;
|
||||
$this->iterator = $iterator;
|
||||
$this->broadcastManager = $broadcastManager;
|
||||
$this->eventsDispatcher = $eventsDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue pushing subscription data to subscribers.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription
|
||||
* @param string $fieldName
|
||||
* @param mixed $root
|
||||
* @return void
|
||||
*/
|
||||
public function queueBroadcast(GraphQLSubscription $subscription, string $fieldName, $root): void
|
||||
{
|
||||
$this->eventsDispatcher->dispatch(
|
||||
new BroadcastSubscriptionEvent($subscription, $fieldName, $root)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push subscription data to subscribers.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription
|
||||
* @param string $fieldName
|
||||
* @param mixed $root
|
||||
* @return void
|
||||
*/
|
||||
public function broadcast(GraphQLSubscription $subscription, string $fieldName, $root): void
|
||||
{
|
||||
$topic = $subscription->decodeTopic($fieldName, $root);
|
||||
|
||||
$subscribers = $this->storage
|
||||
->subscribersByTopic($topic)
|
||||
->filter(function (Subscriber $subscriber) use ($subscription, $root): bool {
|
||||
return $subscription->filter($subscriber, $root);
|
||||
});
|
||||
|
||||
$this->iterator->process(
|
||||
$subscribers,
|
||||
function (Subscriber $subscriber) use ($root): void {
|
||||
$data = $this->graphQL->executeQuery(
|
||||
$subscriber->query,
|
||||
$subscriber->context,
|
||||
$subscriber->args,
|
||||
$subscriber->setRoot($root),
|
||||
$subscriber->operationName
|
||||
);
|
||||
|
||||
$this->broadcastManager->broadcast(
|
||||
$subscriber,
|
||||
$data->jsonSerialize()
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize the subscription.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function authorize(Request $request): Response
|
||||
{
|
||||
return $this->auth->authorize($request)
|
||||
? $this->broadcastManager->authorized($request)
|
||||
: $this->broadcastManager->unauthorized($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use Nuwave\Lighthouse\GraphQL;
|
||||
use Illuminate\Support\Collection;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use Nuwave\Lighthouse\Events\StartExecution;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use Nuwave\Lighthouse\Execution\ExtensionsResponse;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
use Nuwave\Lighthouse\Schema\Types\NotFoundSubscription;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer;
|
||||
|
||||
class SubscriptionRegistry
|
||||
{
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\StorageManager
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\GraphQL
|
||||
*/
|
||||
protected $graphQL;
|
||||
|
||||
/**
|
||||
* A map from operation names to channel names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $subscribers = [];
|
||||
|
||||
/**
|
||||
* Active subscription fields of the schema.
|
||||
*
|
||||
* @var \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription[]
|
||||
*/
|
||||
protected $subscriptions = [];
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer $serializer
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\StorageManager $storage
|
||||
* @param \Nuwave\Lighthouse\GraphQL $graphQL
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(ContextSerializer $serializer, StorageManager $storage, GraphQL $graphQL)
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
$this->storage = $storage;
|
||||
$this->graphQL = $graphQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subscription to registry.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription
|
||||
* @param string $field
|
||||
* @return $this
|
||||
*/
|
||||
public function register(GraphQLSubscription $subscription, string $field): self
|
||||
{
|
||||
$this->subscriptions[$field] = $subscription;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if subscription is registered.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->subscriptions[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription keys.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function keys(): array
|
||||
{
|
||||
return array_keys($this->subscriptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance of subscription.
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription
|
||||
*/
|
||||
public function subscription(string $key): GraphQLSubscription
|
||||
{
|
||||
return $this->subscriptions[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subscription to registry.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param string $channel
|
||||
* @return $this
|
||||
*/
|
||||
public function subscriber(Subscriber $subscriber, string $channel): self
|
||||
{
|
||||
if ($subscriber->channel) {
|
||||
$this->storage->storeSubscriber($subscriber, $channel);
|
||||
}
|
||||
|
||||
$this->subscribers[$subscriber->operationName] = $subscriber->channel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered subscriptions.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function subscriptions(Subscriber $subscriber): Collection
|
||||
{
|
||||
// A subscription can be fired w/out a request so we must make
|
||||
// sure the schema has been generated.
|
||||
$this->graphQL->prepSchema();
|
||||
|
||||
return (new Collection($subscriber->query->definitions))
|
||||
->filter(function (Node $node): bool {
|
||||
return $node instanceof OperationDefinitionNode;
|
||||
})
|
||||
->filter(function (OperationDefinitionNode $node): bool {
|
||||
return $node->operation === 'subscription';
|
||||
})
|
||||
->flatMap(function (OperationDefinitionNode $node): array {
|
||||
return (new Collection($node->selectionSet->selections))
|
||||
->map(function (FieldNode $field): string {
|
||||
return $field->name->value;
|
||||
})
|
||||
->all();
|
||||
})
|
||||
->map(function ($subscriptionField): GraphQLSubscription {
|
||||
return Arr::get(
|
||||
$this->subscriptions,
|
||||
$subscriptionField,
|
||||
new NotFoundSubscription
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the collection of subscribers when a new execution starts.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Events\StartExecution $startExecution
|
||||
* @return void
|
||||
*/
|
||||
public function handleStartExecution(StartExecution $startExecution): void
|
||||
{
|
||||
$this->subscribers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all current subscribers.
|
||||
*
|
||||
* @return \Nuwave\Lighthouse\Execution\ExtensionsResponse
|
||||
*/
|
||||
public function handleBuildExtensionsResponse(): ExtensionsResponse
|
||||
{
|
||||
return new ExtensionsResponse(
|
||||
'lighthouse_subscriptions',
|
||||
[
|
||||
'version' => 1,
|
||||
'channels' => $this->subscribers,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
use Nuwave\Lighthouse\Support\Utils;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
|
||||
use Nuwave\Lighthouse\Schema\Values\FieldValue;
|
||||
use Nuwave\Lighthouse\Exceptions\DefinitionException;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
use Nuwave\Lighthouse\Schema\Directives\SubscriptionDirective;
|
||||
use Nuwave\Lighthouse\Support\Contracts\ProvidesSubscriptionResolver;
|
||||
use Nuwave\Lighthouse\Subscriptions\Exceptions\UnauthorizedSubscriber;
|
||||
|
||||
class SubscriptionResolverProvider implements ProvidesSubscriptionResolver
|
||||
{
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\SubscriptionRegistry
|
||||
*/
|
||||
protected $subscriptionRegistry;
|
||||
|
||||
/**
|
||||
* ResolverProvider constructor.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\SubscriptionRegistry $subscriptionRegistry
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(SubscriptionRegistry $subscriptionRegistry)
|
||||
{
|
||||
$this->subscriptionRegistry = $subscriptionRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a resolver for a subscription field in case no resolver directive is defined.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Values\FieldValue $fieldValue
|
||||
* @return \Closure
|
||||
*
|
||||
* @throws \Nuwave\Lighthouse\Exceptions\DefinitionException
|
||||
*/
|
||||
public function provideSubscriptionResolver(FieldValue $fieldValue): Closure
|
||||
{
|
||||
$fieldName = $fieldValue->getFieldName();
|
||||
|
||||
if ($directive = ASTHelper::directiveDefinition($fieldValue->getField(), SubscriptionDirective::NAME)) {
|
||||
$className = ASTHelper::directiveArgValue($directive, 'class');
|
||||
} else {
|
||||
$className = Str::studly($fieldName);
|
||||
}
|
||||
|
||||
$className = Utils::namespaceClassname(
|
||||
$className,
|
||||
$fieldValue->defaultNamespacesForParent(),
|
||||
function (string $class): bool {
|
||||
return is_subclass_of($class, GraphQLSubscription::class);
|
||||
}
|
||||
);
|
||||
|
||||
if (! $className) {
|
||||
throw new DefinitionException(
|
||||
"No class found for the subscription field {$fieldName}"
|
||||
);
|
||||
}
|
||||
|
||||
/** @var \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription */
|
||||
$subscription = app($className);
|
||||
|
||||
// Subscriptions can only be placed on a single field on the root
|
||||
// query, so there is no need to consider the field path
|
||||
$this->subscriptionRegistry->register(
|
||||
$subscription,
|
||||
$fieldName
|
||||
);
|
||||
|
||||
return function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($subscription, $fieldName) {
|
||||
if ($root instanceof Subscriber) {
|
||||
return $subscription->resolve($root->root, $args, $context, $resolveInfo);
|
||||
}
|
||||
|
||||
$subscriber = new Subscriber(
|
||||
$args,
|
||||
$context,
|
||||
$resolveInfo
|
||||
);
|
||||
|
||||
if (! $subscription->can($subscriber)) {
|
||||
throw new UnauthorizedSubscriber(
|
||||
'Unauthorized subscription request'
|
||||
);
|
||||
}
|
||||
|
||||
$this->subscriptionRegistry->subscriber(
|
||||
$subscriber,
|
||||
$subscription->encodeTopic($subscriber, $fieldName)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Nuwave\Lighthouse\Support\Http\Controllers\SubscriptionController;
|
||||
|
||||
class SubscriptionRouter
|
||||
{
|
||||
/**
|
||||
* Register the routes for pusher based subscriptions.
|
||||
*
|
||||
* @param \Illuminate\Routing\Router $router
|
||||
* @return void
|
||||
*/
|
||||
public function pusher($router): void
|
||||
{
|
||||
$router->post('graphql/subscriptions/auth', [
|
||||
'as' => 'lighthouse.subscriptions.auth',
|
||||
'uses' => SubscriptionController::class.'@authorize',
|
||||
]);
|
||||
|
||||
$router->post('graphql/subscriptions/webhook', [
|
||||
'as' => 'lighthouse.subscriptions.auth',
|
||||
'uses' => SubscriptionController::class.'@webhook',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Nuwave\Lighthouse\Events\StartExecution;
|
||||
use Nuwave\Lighthouse\Events\BuildExtensionsResponse;
|
||||
use Nuwave\Lighthouse\Subscriptions\Iterators\SyncIterator;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventsDispatcher;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator;
|
||||
use Nuwave\Lighthouse\Support\Contracts\ProvidesSubscriptionResolver;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\AuthorizesSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Events\BroadcastSubscriptionEvent;
|
||||
use Nuwave\Lighthouse\Subscriptions\Events\BroadcastSubscriptionListener;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler;
|
||||
|
||||
class SubscriptionServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $eventsDispatcher
|
||||
* @param \Illuminate\Contracts\Config\Repository $configRepository
|
||||
* @return void
|
||||
*/
|
||||
public function boot(EventsDispatcher $eventsDispatcher, ConfigRepository $configRepository): void
|
||||
{
|
||||
$eventsDispatcher->listen(
|
||||
BroadcastSubscriptionEvent::class,
|
||||
BroadcastSubscriptionListener::class
|
||||
);
|
||||
|
||||
$eventsDispatcher->listen(
|
||||
StartExecution::class,
|
||||
SubscriptionRegistry::class.'@handleStartExecution'
|
||||
);
|
||||
|
||||
$eventsDispatcher->listen(
|
||||
BuildExtensionsResponse::class,
|
||||
SubscriptionRegistry::class.'@handleBuildExtensionsResponse'
|
||||
);
|
||||
|
||||
// Register the routes for the configured broadcaster. The specific
|
||||
// method that is used can be changed, so we retrieve its name
|
||||
// dynamically and then call it with an instance of 'router'.
|
||||
$broadcaster = $configRepository->get('lighthouse.subscriptions.broadcaster');
|
||||
if ($routesMethod = $configRepository->get("lighthouse.subscriptions.broadcasters.{$broadcaster}.routes")) {
|
||||
[$router, $method] = Str::parseCallback($routesMethod, 'pusher');
|
||||
|
||||
call_user_func(
|
||||
[$this->app->make($router), $method],
|
||||
$this->app->make('router')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register subscription services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(BroadcastManager::class);
|
||||
$this->app->singleton(SubscriptionRegistry::class);
|
||||
$this->app->singleton(StoresSubscriptions::class, StorageManager::class);
|
||||
|
||||
$this->app->bind(ContextSerializer::class, Serializer::class);
|
||||
$this->app->bind(AuthorizesSubscriptions::class, Authorizer::class);
|
||||
$this->app->bind(SubscriptionIterator::class, SyncIterator::class);
|
||||
$this->app->bind(SubscriptionExceptionHandler::class, ExceptionHandler::class);
|
||||
$this->app->bind(BroadcastsSubscriptions::class, SubscriptionBroadcaster::class);
|
||||
$this->app->bind(ProvidesSubscriptionResolver::class, SubscriptionResolverProvider::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user