new Deps
This commit is contained in:
+31
-30
@@ -4,9 +4,9 @@ namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Illuminate\Support\Str;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\AuthorizesSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler;
|
||||
|
||||
class Authorizer implements AuthorizesSubscriptions
|
||||
@@ -26,12 +26,6 @@ class Authorizer implements AuthorizesSubscriptions
|
||||
*/
|
||||
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,
|
||||
@@ -42,47 +36,54 @@ class Authorizer implements AuthorizesSubscriptions
|
||||
$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()
|
||||
);
|
||||
$channel = $request->input('channel_name');
|
||||
if (! is_string($channel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $subscriber) {
|
||||
$channel = $this->sanitizeChannelName($channel);
|
||||
|
||||
$subscriber = $this->storage->subscriberByChannel($channel);
|
||||
if ($subscriber === null) {
|
||||
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);
|
||||
}
|
||||
);
|
||||
/** @var \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription */
|
||||
foreach ($subscriptions as $subscription) {
|
||||
if (! $subscription->authorize($subscriber, $request)) {
|
||||
$this->storage->deleteSubscriber($subscriber->channel);
|
||||
|
||||
if (! $authorized) {
|
||||
$this->storage->deleteSubscriber($subscriber->channel);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $authorized;
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->exceptionHandler->handleAuthError($e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the prefix "presence-" from the channel name.
|
||||
*
|
||||
* Laravel Echo prefixes channel names with "presence-", but we don't.
|
||||
*/
|
||||
protected function sanitizeChannelName(string $channelName): string
|
||||
{
|
||||
if (Str::startsWith($channelName, 'presence-')) {
|
||||
return Str::substr($channelName, 9);
|
||||
}
|
||||
|
||||
return $channelName;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-29
@@ -2,13 +2,16 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Pusher\Pusher;
|
||||
use RuntimeException;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler as LaravelExceptionHandler;
|
||||
use Illuminate\Support\Arr;
|
||||
use Nuwave\Lighthouse\Support\DriverManager;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Broadcasters\EchoBroadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Broadcasters\LogBroadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Broadcasters\PusherBroadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
use Nuwave\Lighthouse\Support\DriverManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Pusher\Pusher;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @method void broadcast(\Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber, array $data)
|
||||
@@ -18,42 +21,24 @@ use Nuwave\Lighthouse\Subscriptions\Broadcasters\PusherBroadcaster;
|
||||
*/
|
||||
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
|
||||
* @param array<string, mixed> $config
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function createPusherDriver(array $config): PusherBroadcaster
|
||||
{
|
||||
@@ -71,17 +56,23 @@ class BroadcastManager extends DriverManager
|
||||
|
||||
$pusher = new Pusher($appKey, $appSecret, $appId, $options);
|
||||
|
||||
return new PusherBroadcaster($pusher);
|
||||
if ($driverConfig['log'] ?? false) {
|
||||
$pusher->setLogger($this->app->make(LoggerInterface::class));
|
||||
}
|
||||
|
||||
return new PusherBroadcaster($pusher, $this->app->make(LaravelExceptionHandler::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance of log driver.
|
||||
*
|
||||
* @param mixed[] $config
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Broadcasters\LogBroadcaster
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
protected function createLogDriver(array $config): LogBroadcaster
|
||||
{
|
||||
return new LogBroadcaster($config);
|
||||
}
|
||||
|
||||
protected function createEchoDriver(): EchoBroadcaster
|
||||
{
|
||||
return $this->app->make(EchoBroadcaster::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions;
|
||||
|
||||
class BroadcastSubscriptionJob implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The subscription field that was requested.
|
||||
*
|
||||
* @var \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription
|
||||
*/
|
||||
public $subscription;
|
||||
|
||||
/**
|
||||
* The name of the field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $fieldName;
|
||||
|
||||
/**
|
||||
* The root element to be passed when resolving the subscription.
|
||||
*
|
||||
* @var mixed User defined.
|
||||
*/
|
||||
public $root;
|
||||
|
||||
public function __construct(GraphQLSubscription $subscription, string $fieldName, $root)
|
||||
{
|
||||
$this->subscription = $subscription;
|
||||
$this->fieldName = $fieldName;
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
public function handle(BroadcastsSubscriptions $broadcaster): void
|
||||
{
|
||||
$broadcaster->broadcast($this->subscription, $this->fieldName, $this->root);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Broadcasters;
|
||||
|
||||
use Illuminate\Broadcasting\BroadcastManager;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Events\EchoSubscriptionEvent;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
|
||||
class EchoBroadcaster implements Broadcaster
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Broadcasting\BroadcastManager
|
||||
*/
|
||||
protected $broadcaster;
|
||||
|
||||
public function __construct(BroadcastManager $broadcaster)
|
||||
{
|
||||
$this->broadcaster = $broadcaster;
|
||||
}
|
||||
|
||||
public function broadcast(Subscriber $subscriber, $data): void
|
||||
{
|
||||
$this->broadcaster->event(
|
||||
new EchoSubscriptionEvent($subscriber->channel, $data)
|
||||
);
|
||||
}
|
||||
|
||||
public function authorized(Request $request): JsonResponse
|
||||
{
|
||||
$userId = md5(
|
||||
$request->input('channel_name')
|
||||
.$request->input('socket_id')
|
||||
);
|
||||
|
||||
return new JsonResponse([
|
||||
'channel_data' => [
|
||||
'user_id' => $userId,
|
||||
'user_info' => [],
|
||||
],
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function unauthorized(Request $request): JsonResponse
|
||||
{
|
||||
return new JsonResponse([
|
||||
'message' => 'Unauthorized',
|
||||
], 403);
|
||||
}
|
||||
|
||||
public function hook(Request $request): JsonResponse
|
||||
{
|
||||
// Does nothing.
|
||||
// The redis broadcaster has the lighthouse:subscribe command to take care of cleaning vacant channels.
|
||||
|
||||
return new JsonResponse([
|
||||
'message' => 'okay',
|
||||
], 200);
|
||||
}
|
||||
}
|
||||
+19
-42
@@ -2,89 +2,66 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Broadcasters;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
|
||||
class LogBroadcaster implements Broadcaster
|
||||
{
|
||||
/**
|
||||
* The user-defined configuration options.
|
||||
*
|
||||
* @var mixed[]
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* A map from channel names to data.
|
||||
*
|
||||
* @var mixed
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $broadcasts = [];
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @return void
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
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);
|
||||
return new JsonResponse([
|
||||
'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);
|
||||
return new JsonResponse([
|
||||
'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']);
|
||||
return new JsonResponse([
|
||||
'message' => 'okay',
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to subscriber.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function broadcast(Subscriber $subscriber, array $data): void
|
||||
public function broadcast(Subscriber $subscriber, $data): void
|
||||
{
|
||||
$this->broadcasts[$subscriber->channel] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data that is being broadcast.
|
||||
*
|
||||
* @param string|null $key
|
||||
* @return array|null
|
||||
* @return mixed The data that is being broadcast
|
||||
*/
|
||||
public function broadcasts(?string $key = null): ?array
|
||||
public function broadcasts(?string $key = null)
|
||||
{
|
||||
return Arr::get($this->broadcasts, $key);
|
||||
}
|
||||
@@ -92,7 +69,7 @@ class LogBroadcaster implements Broadcaster
|
||||
/**
|
||||
* Get configuration options.
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function config(): array
|
||||
{
|
||||
|
||||
+36
-60
@@ -2,106 +2,82 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Broadcasters;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
use Pusher\ApiErrorException;
|
||||
use Pusher\Pusher;
|
||||
|
||||
class PusherBroadcaster implements Broadcaster
|
||||
{
|
||||
const EVENT_NAME = 'lighthouse-subscription';
|
||||
|
||||
/**
|
||||
* @var \Pusher\Pusher
|
||||
*/
|
||||
protected $pusher;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Debug\ExceptionHandler
|
||||
*/
|
||||
protected $exceptionHandler;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Create instance of pusher broadcaster.
|
||||
*
|
||||
* @param \Pusher\Pusher $pusher
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($pusher)
|
||||
public function __construct(Pusher $pusher, ExceptionHandler $exceptionHandler)
|
||||
{
|
||||
$this->pusher = $pusher;
|
||||
$this->exceptionHandler = $exceptionHandler;
|
||||
$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(
|
||||
$data = \Safe\json_decode(
|
||||
$this->pusher->socket_auth($channel, $socketId),
|
||||
true
|
||||
);
|
||||
|
||||
return response()->json($data, 200);
|
||||
return new JsonResponse($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);
|
||||
return new JsonResponse([
|
||||
'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')
|
||||
);
|
||||
});
|
||||
foreach ($request->input('events', []) as $event) {
|
||||
if ($event['name'] === 'channel_vacated') {
|
||||
$this->storage->deleteSubscriber($event['channel']);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'okay']);
|
||||
return new JsonResponse(['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
|
||||
public function broadcast(Subscriber $subscriber, $data): void
|
||||
{
|
||||
$this->pusher->trigger(
|
||||
$subscriber->channel,
|
||||
self::EVENT_NAME,
|
||||
[
|
||||
'more' => true,
|
||||
'result' => $data,
|
||||
]
|
||||
);
|
||||
try {
|
||||
$this->pusher->trigger(
|
||||
$subscriber->channel,
|
||||
self::EVENT_NAME,
|
||||
[
|
||||
'more' => true,
|
||||
'result' => $data,
|
||||
]
|
||||
);
|
||||
} catch (ApiErrorException $e) {
|
||||
$this->exceptionHandler->report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -7,9 +7,8 @@ use Illuminate\Http\Request;
|
||||
interface AuthorizesSubscriptions
|
||||
{
|
||||
/**
|
||||
* Authorize subscription request.
|
||||
* Is the subscription request authorized?
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(Request $request);
|
||||
|
||||
@@ -7,10 +7,11 @@ use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
|
||||
interface Broadcaster
|
||||
{
|
||||
public const EVENT_NAME = 'lighthouse-subscription';
|
||||
|
||||
/**
|
||||
* Handle authorized subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function authorized(Request $request);
|
||||
@@ -18,7 +19,6 @@ interface Broadcaster
|
||||
/**
|
||||
* Handle unauthorized subscription request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function unauthorized(Request $request);
|
||||
@@ -26,7 +26,6 @@ interface Broadcaster
|
||||
/**
|
||||
* Handle subscription web hook.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function hook(Request $request);
|
||||
@@ -34,9 +33,8 @@ interface Broadcaster
|
||||
/**
|
||||
* Send data to subscriber.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param mixed[] $data
|
||||
* @param mixed $data The data to broadcast
|
||||
* @return void
|
||||
*/
|
||||
public function broadcast(Subscriber $subscriber, array $data);
|
||||
public function broadcast(Subscriber $subscriber, $data);
|
||||
}
|
||||
|
||||
+1
-7
@@ -10,18 +10,13 @@ interface BroadcastsSubscriptions
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -29,7 +24,6 @@ interface BroadcastsSubscriptions
|
||||
/**
|
||||
* Authorize the subscription.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function authorize(Request $request);
|
||||
|
||||
@@ -9,7 +9,6 @@ interface ContextSerializer
|
||||
/**
|
||||
* Serialize the context.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(GraphQLContext $context);
|
||||
@@ -17,7 +16,6 @@ interface ContextSerializer
|
||||
/**
|
||||
* Unserialize the context.
|
||||
*
|
||||
* @param string $context
|
||||
* @return \Nuwave\Lighthouse\Support\Contracts\GraphQLContext
|
||||
*/
|
||||
public function unserialize(string $context);
|
||||
|
||||
+4
-18
@@ -7,43 +7,29 @@ use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
interface StoresSubscriptions
|
||||
{
|
||||
/**
|
||||
* Get subscriber by request.
|
||||
* Find a subscriber by its channel key.
|
||||
*
|
||||
* @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.
|
||||
* Get all subscribers for a topic.
|
||||
*
|
||||
* @param string $topic
|
||||
* @return \Illuminate\Support\Collection<\Nuwave\Lighthouse\Subscriptions\Subscriber>
|
||||
*/
|
||||
public function subscribersByTopic(string $topic);
|
||||
|
||||
/**
|
||||
* Store subscription.
|
||||
* Store subscriber for a topic.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param string $topic
|
||||
* @return void
|
||||
*/
|
||||
public function storeSubscriber(Subscriber $subscriber, string $topic);
|
||||
|
||||
/**
|
||||
* Delete subscriber.
|
||||
* Delete subscriber by its channel key.
|
||||
*
|
||||
* @param string $channel
|
||||
* @return \Nuwave\Lighthouse\Subscriptions\Subscriber|null
|
||||
*/
|
||||
public function deleteSubscriber(string $channel);
|
||||
|
||||
-2
@@ -9,7 +9,6 @@ interface SubscriptionExceptionHandler
|
||||
/**
|
||||
* Handle authentication error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleAuthError(Throwable $e);
|
||||
@@ -17,7 +16,6 @@ interface SubscriptionExceptionHandler
|
||||
/**
|
||||
* Handle broadcast error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleBroadcastError(Throwable $e);
|
||||
|
||||
+11
-5
@@ -8,12 +8,18 @@ use Illuminate\Support\Collection;
|
||||
interface SubscriptionIterator
|
||||
{
|
||||
/**
|
||||
* Process collection of items.
|
||||
* Process subscribers through the given callbacks.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param \Closure $cb
|
||||
* @param \Closure|null $error
|
||||
* @param \Illuminate\Support\Collection<\Nuwave\Lighthouse\Subscriptions\Subscriber> $subscribers
|
||||
* The subscribers that receive the current subscription.
|
||||
*
|
||||
* @param \Closure $handleSubscriber
|
||||
* Receives each subscriber in the passed in collection.
|
||||
* function(\Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber)
|
||||
*
|
||||
* @param \Closure|null $handleError
|
||||
* Is called when $handleSubscriber throws.
|
||||
* @return void
|
||||
*/
|
||||
public function process(Collection $items, Closure $cb, Closure $error = null);
|
||||
public function process(Collection $subscribers, Closure $handleSubscriber, Closure $handleError = null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Directives;
|
||||
|
||||
use Closure;
|
||||
use Nuwave\Lighthouse\Execution\Utils\Subscription;
|
||||
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
|
||||
use Nuwave\Lighthouse\Schema\Values\FieldValue;
|
||||
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
|
||||
|
||||
class BroadcastDirective extends BaseDirective implements FieldMiddleware
|
||||
{
|
||||
public static function definition(): string
|
||||
{
|
||||
return /** @lang GraphQL */ <<<'GRAPHQL'
|
||||
"""
|
||||
Broadcast the results of a mutation to subscribed clients.
|
||||
"""
|
||||
directive @broadcast(
|
||||
"""
|
||||
Name of the subscription that should be retriggered as a result of this operation.
|
||||
"""
|
||||
subscription: String!
|
||||
|
||||
"""
|
||||
Specify whether or not the job should be queued.
|
||||
This defaults to the global config option `lighthouse.subscriptions.queue_broadcasts`.
|
||||
"""
|
||||
shouldQueue: Boolean
|
||||
) repeatable on FIELD_DEFINITION
|
||||
GRAPHQL;
|
||||
}
|
||||
|
||||
public function handleField(FieldValue $fieldValue, Closure $next): FieldValue
|
||||
{
|
||||
// Ensure this is run after the other field middleware directives
|
||||
$fieldValue = $next($fieldValue);
|
||||
|
||||
$subscriptionField = $this->directiveArgValue('subscription');
|
||||
$shouldQueue = $this->directiveArgValue('shouldQueue');
|
||||
|
||||
$fieldValue->resultHandler(function ($root) use ($subscriptionField, $shouldQueue) {
|
||||
Subscription::broadcast($subscriptionField, $root, $shouldQueue);
|
||||
|
||||
return $root;
|
||||
});
|
||||
|
||||
return $fieldValue;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Directives;
|
||||
|
||||
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
|
||||
use Nuwave\Lighthouse\Support\Contracts\Directive;
|
||||
|
||||
/**
|
||||
* This directive exists as a placeholder and can be used
|
||||
* to point to a custom subscription class.
|
||||
*
|
||||
* @see \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription
|
||||
*/
|
||||
class SubscriptionDirective extends BaseDirective implements Directive
|
||||
{
|
||||
public const NAME = 'subscription';
|
||||
|
||||
public static function definition(): string
|
||||
{
|
||||
return /** @lang GraphQL */ <<<'GRAPHQL'
|
||||
"""
|
||||
Reference a class to handle the broadcasting of a subscription to clients.
|
||||
The given class must extend `\Nuwave\Lighthouse\Schema\Types\GraphQLSubscription`.
|
||||
"""
|
||||
directive @subscription(
|
||||
"""
|
||||
A reference to a subclass of `\Nuwave\Lighthouse\Schema\Types\GraphQLSubscription`.
|
||||
"""
|
||||
class: String!
|
||||
) on FIELD_DEFINITION
|
||||
GRAPHQL;
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
<?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
@@ -1,38 +0,0 @@
|
||||
<?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,44 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\Broadcaster;
|
||||
|
||||
class EchoSubscriptionEvent implements ShouldBroadcastNow
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $channel;
|
||||
|
||||
/**
|
||||
* @var mixed The data to broadcast.
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param mixed $data The data to broadcast.
|
||||
*/
|
||||
public function __construct(string $channel, $data)
|
||||
{
|
||||
$this->channel = $channel;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function broadcastOn(): Channel
|
||||
{
|
||||
return new Channel($this->channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an event name.
|
||||
*
|
||||
* Allows the echo client to receive this event using .listen('.lighthouse.subscription', () => ...).
|
||||
*/
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return Broadcaster::EVENT_NAME;
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,13 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Throwable;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class ExceptionHandler implements SubscriptionExceptionHandler
|
||||
{
|
||||
/**
|
||||
* Handle authentication error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleAuthError(Throwable $e): void
|
||||
{
|
||||
@@ -20,9 +17,6 @@ class ExceptionHandler implements SubscriptionExceptionHandler
|
||||
|
||||
/**
|
||||
* Handle broadcast error.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public function handleBroadcastError(Throwable $e): void
|
||||
{
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Iterators;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Auth\Factory as AuthFactory;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
use Nuwave\Lighthouse\Subscriptions\SubscriptionGuard;
|
||||
|
||||
/**
|
||||
* Logs in the subscriber as their subscription is resolved.
|
||||
*/
|
||||
class AuthenticatingSyncIterator implements SubscriptionIterator
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
protected $configRepository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Auth\Factory
|
||||
*/
|
||||
protected $authFactory;
|
||||
|
||||
public function __construct(ConfigRepository $configRepository, AuthFactory $authFactory)
|
||||
{
|
||||
$this->configRepository = $configRepository;
|
||||
$this->authFactory = $authFactory;
|
||||
}
|
||||
|
||||
public function process(Collection $subscribers, Closure $handleSubscriber, Closure $handleError = null): void
|
||||
{
|
||||
// Store the previous default guard name so we can restore it after we're done
|
||||
$previousGuardName = $this->configRepository->get('auth.defaults.guard');
|
||||
|
||||
// Set our subscription guard as the default guard for the application
|
||||
$this->authFactory->shouldUse(SubscriptionGuard::GUARD_NAME);
|
||||
|
||||
/** @var \Nuwave\Lighthouse\Subscriptions\SubscriptionGuard $guard */
|
||||
$guard = $this->authFactory->guard(SubscriptionGuard::GUARD_NAME);
|
||||
|
||||
$subscribers->each(static function (Subscriber $item) use ($handleSubscriber, $handleError, $guard): void {
|
||||
// If there is an authenticated user set in the context, set that user as the authenticated user
|
||||
$user = $item->context->user();
|
||||
if ($user !== null) {
|
||||
$guard->setUser($user);
|
||||
}
|
||||
|
||||
try {
|
||||
$handleSubscriber($item);
|
||||
} catch (Exception $e) {
|
||||
if ($handleError === null) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$handleError($e);
|
||||
} finally {
|
||||
// Unset the authenticated user after each iteration to restore the guard to a unauthenticated state
|
||||
$guard->reset();
|
||||
}
|
||||
});
|
||||
|
||||
// Restore the previous default guard name
|
||||
$this->authFactory->shouldUse($previousGuardName);
|
||||
}
|
||||
}
|
||||
@@ -9,25 +9,17 @@ 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
|
||||
public function process(Collection $subscribers, Closure $handleSubscriber, Closure $handleError = null): void
|
||||
{
|
||||
$items->each(function ($item) use ($cb, $error): void {
|
||||
$subscribers->each(static function ($item) use ($handleSubscriber, $handleError): void {
|
||||
try {
|
||||
$cb($item);
|
||||
$handleSubscriber($item);
|
||||
} catch (Exception $e) {
|
||||
if (! $error) {
|
||||
if ($handleError === null) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$error($e);
|
||||
$handleError($e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+7
-20
@@ -2,34 +2,27 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Queue\SerializesAndRestoresModelIdentifiers;
|
||||
use Illuminate\Support\Arr;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer;
|
||||
use Nuwave\Lighthouse\Support\Contracts\CreatesContext;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer;
|
||||
|
||||
class Serializer implements ContextSerializer
|
||||
{
|
||||
use SerializesAndRestoresModelIdentifiers;
|
||||
|
||||
/**
|
||||
* @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();
|
||||
@@ -44,16 +37,10 @@ class Serializer implements ContextSerializer
|
||||
'server' => Arr::except($request->server->all(), ['HTTP_AUTHORIZATION']),
|
||||
'content' => $request->getContent(),
|
||||
],
|
||||
'user' => serialize($context->user()),
|
||||
'user' => $this->getSerializedPropertyValue($context->user()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize the context.
|
||||
*
|
||||
* @param string $context
|
||||
* @return \Nuwave\Lighthouse\Support\Contracts\GraphQLContext
|
||||
*/
|
||||
public function unserialize(string $context): GraphQLContext
|
||||
{
|
||||
[
|
||||
@@ -73,7 +60,7 @@ class Serializer implements ContextSerializer
|
||||
|
||||
$request->setUserResolver(
|
||||
function () use ($rawUser) {
|
||||
return unserialize($rawUser);
|
||||
return $this->getRestoredPropertyValue($rawUser);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Storage;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Cache\Factory as CacheFactory;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
|
||||
class CacheStorageManager implements StoresSubscriptions
|
||||
{
|
||||
/**
|
||||
* The cache key for topics.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TOPIC_KEY = 'graphql.topic';
|
||||
|
||||
/**
|
||||
* The cache key for subscribers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const SUBSCRIBER_KEY = 'graphql.subscriber';
|
||||
|
||||
/**
|
||||
* The cache to store channels and topics.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Cache\Repository
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The time to live for items in the cache.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $ttl;
|
||||
|
||||
public function __construct(CacheFactory $cacheFactory, ConfigRepository $config)
|
||||
{
|
||||
$storage = $config->get('lighthouse.subscriptions.storage') ?? 'file';
|
||||
if (! is_string($storage)) {
|
||||
throw new Exception('Config setting lighthouse.subscriptions.storage must be a string or `null`, got: '.\Safe\json_encode($storage));
|
||||
}
|
||||
$this->cache = $cacheFactory->store($storage);
|
||||
|
||||
$ttl = $config->get('lighthouse.subscriptions.storage_ttl');
|
||||
if (! is_null($ttl) && ! is_int($ttl)) {
|
||||
throw new Exception('Config setting lighthouse.subscriptions.storage_ttl must be a int or `null`, got: '.\Safe\json_encode($ttl));
|
||||
}
|
||||
$this->ttl = $ttl;
|
||||
}
|
||||
|
||||
public function subscriberByChannel(string $channel): ?Subscriber
|
||||
{
|
||||
return $this->cache->get(self::channelKey($channel));
|
||||
}
|
||||
|
||||
public function subscribersByTopic(string $topic): Collection
|
||||
{
|
||||
/** @var \Illuminate\Support\Collection<\Nuwave\Lighthouse\Subscriptions\Subscriber> $subscribers */
|
||||
$subscribers = $this
|
||||
->retrieveTopic(self::topicKey($topic))
|
||||
->map(function (string $channel): ?Subscriber {
|
||||
return $this->subscriberByChannel($channel);
|
||||
})
|
||||
->filter();
|
||||
|
||||
return $subscribers;
|
||||
}
|
||||
|
||||
public function storeSubscriber(Subscriber $subscriber, string $topic): void
|
||||
{
|
||||
$subscriber->topic = $topic;
|
||||
$this->addSubscriberToTopic($subscriber);
|
||||
|
||||
$channelKey = self::channelKey($subscriber->channel);
|
||||
if ($this->ttl === null) {
|
||||
$this->cache->forever($channelKey, $subscriber);
|
||||
} else {
|
||||
// TODO: Change to just pass the ttl directly when support for Laravel <=5.7 is dropped
|
||||
$this->cache->put($channelKey, $subscriber, Carbon::now()->addSeconds($this->ttl));
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteSubscriber(string $channel): ?Subscriber
|
||||
{
|
||||
$subscriber = $this->cache->pull(self::channelKey($channel));
|
||||
|
||||
if ($subscriber !== null) {
|
||||
$this->removeSubscriberFromTopic($subscriber);
|
||||
}
|
||||
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a topic (list of channels) in the cache.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection<string> $topic
|
||||
*/
|
||||
protected function storeTopic(string $key, Collection $topic): void
|
||||
{
|
||||
if ($this->ttl === null) {
|
||||
$this->cache->forever($key, $topic);
|
||||
} else {
|
||||
// TODO: Change to just pass the ttl directly when support for Laravel <=5.7 is dropped
|
||||
$this->cache->put($key, $topic, Carbon::now()->addSeconds($this->ttl));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a topic (list of channels) from the cache.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection<string>
|
||||
*/
|
||||
protected function retrieveTopic(string $key): Collection
|
||||
{
|
||||
return $this->cache->get($key, new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the subscriber to the topic they subscribe to.
|
||||
*/
|
||||
protected function addSubscriberToTopic(Subscriber $subscriber): void
|
||||
{
|
||||
$topicKey = self::topicKey($subscriber->topic);
|
||||
|
||||
$topic = $this->retrieveTopic($topicKey);
|
||||
$topic->push($subscriber->channel);
|
||||
$this->storeTopic($topicKey, $topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the subscriber from the topic they are subscribed to.
|
||||
*/
|
||||
protected function removeSubscriberFromTopic(Subscriber $subscriber): void
|
||||
{
|
||||
$topicKey = self::topicKey($subscriber->topic);
|
||||
$channelKeyToRemove = self::channelKey($subscriber->channel);
|
||||
|
||||
$topicWithoutSubscriber = $this
|
||||
->retrieveTopic($topicKey)
|
||||
->reject(function (string $channel) use ($channelKeyToRemove): bool {
|
||||
return self::channelKey($channel) === $channelKeyToRemove;
|
||||
});
|
||||
|
||||
if ($topicWithoutSubscriber->isEmpty()) {
|
||||
$this->cache->forget($topicKey);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->storeTopic($topicKey, $topicWithoutSubscriber);
|
||||
}
|
||||
|
||||
protected static function channelKey(string $channel): string
|
||||
{
|
||||
return self::SUBSCRIBER_KEY.".{$channel}";
|
||||
}
|
||||
|
||||
protected static function topicKey(string $topic): string
|
||||
{
|
||||
return self::TOPIC_KEY.".{$topic}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions\Storage;
|
||||
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Illuminate\Contracts\Redis\Factory as RedisFactory;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Subscriber;
|
||||
|
||||
/**
|
||||
* Stores subscribers and topics in redis.
|
||||
* - Topics are subscriptions like "userCreated" or "userDeleted".
|
||||
* - Subscribers are clients that are listening to channels like "private-lighthouse-a7ef3d".
|
||||
*
|
||||
* This manager stores a SET of subscriber channels and the subscribers itself like this:
|
||||
* - graphql.topic.userCreated = [ "presence-lighthouse-1", "presence-lighthouse-2", ... ]
|
||||
* - graphql.topic.userDeleted = [ "presence-lighthouse-5", "presence-lighthouse-6", ... ]
|
||||
* - graphql.subscriber.presence-lighthouse-1 = { query: "{ id, name }" }
|
||||
* - graphql.subscriber.presence-lighthouse-2 = { query: "{ name, created_at }" }
|
||||
*/
|
||||
class RedisStorageManager implements StoresSubscriptions
|
||||
{
|
||||
public const TOPIC_KEY = 'graphql.topic';
|
||||
|
||||
public const SUBSCRIBER_KEY = 'graphql.subscriber';
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The time to live in seconds for items in the cache.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $ttl;
|
||||
|
||||
public function __construct(ConfigRepository $config, RedisFactory $redis)
|
||||
{
|
||||
$this->connection = $redis->connection(
|
||||
$config->get('lighthouse.subscriptions.broadcasters.echo.connection') ?? 'default'
|
||||
);
|
||||
$this->ttl = $config->get('lighthouse.subscriptions.storage_ttl');
|
||||
}
|
||||
|
||||
public function subscriberByChannel(string $channel): ?Subscriber
|
||||
{
|
||||
return $this->getSubscriber(
|
||||
$this->channelKey($channel)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection<\Nuwave\Lighthouse\Subscriptions\Subscriber>
|
||||
*/
|
||||
public function subscribersByTopic(string $topic): Collection
|
||||
{
|
||||
// As explained in storeSubscriber, we use redis sets to store the names of subscribers of a topic.
|
||||
// We can retrieve all members of a set using the command smembers.
|
||||
$subscriberIds = $this->connection->command('smembers', [$this->topicKey($topic)]);
|
||||
if (count($subscriberIds) === 0) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
// Since we store the individual subscribers with a prefix,
|
||||
// but not in the set, we have to add the prefix here.
|
||||
$subscriberIds = array_map([$this, 'channelKey'], $subscriberIds);
|
||||
|
||||
// Using the mget command, we can retrieve multiple values from redis.
|
||||
// This is like using multiple get calls (getSubscriber uses the get command).
|
||||
$subscribers = $this->connection->command('mget', [$subscriberIds]);
|
||||
|
||||
return (new Collection($subscribers))
|
||||
->map(function (string $subscriber) {
|
||||
return $this->unserialize($subscriber);
|
||||
})
|
||||
->filter();
|
||||
}
|
||||
|
||||
public function storeSubscriber(Subscriber $subscriber, string $topic): void
|
||||
{
|
||||
$subscriber->topic = $topic;
|
||||
|
||||
// In contrast to the CacheStorageManager, we use redis sets.
|
||||
// Instead of reading the entire list, adding the subscriber and storing the list;
|
||||
// we simply add the name of the subscriber to the set of subscribers of this topic using the sadd command...
|
||||
$topicKey = $this->topicKey($topic);
|
||||
$this->connection->command('sadd', [
|
||||
$topicKey,
|
||||
$subscriber->channel,
|
||||
]);
|
||||
// ...and refresh the ttl of this set as well.
|
||||
if ($this->ttl !== null) {
|
||||
$this->connection->command('expire', [$topicKey, $this->ttl]);
|
||||
}
|
||||
|
||||
// Lastly, we store the subscriber as a serialized string...
|
||||
$setCommand = 'set';
|
||||
$setArguments = [
|
||||
$this->channelKey($subscriber->channel),
|
||||
$this->serialize($subscriber),
|
||||
];
|
||||
if ($this->ttl !== null) {
|
||||
$setCommand = 'setex';
|
||||
array_splice($setArguments, 1, 0, [$this->ttl]);
|
||||
}
|
||||
$this->connection->command($setCommand, $setArguments);
|
||||
}
|
||||
|
||||
public function deleteSubscriber(string $channel): ?Subscriber
|
||||
{
|
||||
$key = $this->channelKey($channel);
|
||||
$subscriber = $this->getSubscriber($key);
|
||||
|
||||
if ($subscriber !== null) {
|
||||
// Like in storeSubscriber (but in reverse), we delete the subscriber...
|
||||
$this->connection->command('del', [$key]);
|
||||
// ...and remove it from the set of subscribers of this topic.
|
||||
$this->connection->command('srem', [
|
||||
$this->topicKey($subscriber->topic),
|
||||
$channel,
|
||||
]);
|
||||
}
|
||||
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
protected function getSubscriber(string $channelKey): ?Subscriber
|
||||
{
|
||||
$subscriber = $this->unserialize(
|
||||
$this->connection->command('get', [$channelKey])
|
||||
);
|
||||
|
||||
// unserialize could return false, so we make sure to only return a Subscriber or null
|
||||
if ($subscriber instanceof Subscriber) {
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function channelKey(string $channel): string
|
||||
{
|
||||
return self::SUBSCRIBER_KEY.'.'.$channel;
|
||||
}
|
||||
|
||||
protected function topicKey(string $topic): string
|
||||
{
|
||||
return self::TOPIC_KEY.'.'.$topic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to serialize.
|
||||
* @return mixed Storable value.
|
||||
* @see \Illuminate\Cache\RedisStore::serialize
|
||||
*/
|
||||
protected function serialize($value)
|
||||
{
|
||||
$isProperNumber = is_numeric($value)
|
||||
&& ($value !== INF && $value !== -INF)
|
||||
&& ! is_nan(floatval($value));
|
||||
|
||||
return $isProperNumber
|
||||
? $value
|
||||
: serialize($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to unserialize.
|
||||
* @return mixed Unserialized value.
|
||||
*/
|
||||
protected function unserialize($value)
|
||||
{
|
||||
if (false === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_numeric($value)
|
||||
? $value
|
||||
: unserialize($value);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+88
-59
@@ -2,126 +2,160 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Serializable;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
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;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
use Serializable;
|
||||
|
||||
class Subscriber implements Serializable
|
||||
{
|
||||
const MISSING_OPERATION_NAME = 'Must pass an operation name when using a subscription.';
|
||||
|
||||
/**
|
||||
* A unique key for the subscriber's channel.
|
||||
*
|
||||
* This has to be unique for each subscriber, because each of them can send a different
|
||||
* query and must receive a response that is specifically tailored towards that.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $channel;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
* The topic subscribed to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $root;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $context;
|
||||
public $topic;
|
||||
|
||||
/**
|
||||
* The contents of the query.
|
||||
*
|
||||
* @var \GraphQL\Language\AST\DocumentNode
|
||||
*/
|
||||
public $query;
|
||||
|
||||
/**
|
||||
* The name of the queried field.
|
||||
*
|
||||
* Guaranteed be be unique because of
|
||||
* @see \GraphQL\Validator\Rules\SingleFieldSubscription
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $operationName;
|
||||
public $fieldName;
|
||||
|
||||
/**
|
||||
* @param mixed[] $args
|
||||
* @param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context
|
||||
* @param \GraphQL\Type\Definition\ResolveInfo $resolveInfo
|
||||
* @return void
|
||||
* The root element of the query.
|
||||
*
|
||||
* @throws \Nuwave\Lighthouse\Exceptions\SubscriptionException
|
||||
* @var mixed Can be anything.
|
||||
*/
|
||||
public $root;
|
||||
|
||||
/**
|
||||
* The args passed to the subscription query.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* The variables passed to the subscription query.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $variables;
|
||||
|
||||
/**
|
||||
* The context passed to the query.
|
||||
*
|
||||
* @var \Nuwave\Lighthouse\Support\Contracts\GraphQLContext
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
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->fieldName = $resolveInfo->fieldName;
|
||||
$this->channel = self::uniqueChannelName();
|
||||
$this->args = $args;
|
||||
$this->variables = $resolveInfo->variableValues;
|
||||
$this->context = $context;
|
||||
|
||||
$documentNode = new DocumentNode([]);
|
||||
$documentNode->definitions = $resolveInfo->fragments;
|
||||
$documentNode->definitions[] = $resolveInfo->operation;
|
||||
$this->query = $documentNode;
|
||||
/**
|
||||
* Must be here, since webonyx/graphql-php validated the subscription.
|
||||
*
|
||||
* @var \GraphQL\Language\AST\OperationDefinitionNode $operation
|
||||
*/
|
||||
$operation = $resolveInfo->operation;
|
||||
|
||||
$this->query = new DocumentNode([
|
||||
'definitions' => new NodeList(array_merge(
|
||||
$resolveInfo->fragments,
|
||||
[$operation]
|
||||
)),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize subscription from a JSON string.
|
||||
*
|
||||
* @param string $subscription
|
||||
* @return $this
|
||||
*/
|
||||
public function unserialize($subscription): self
|
||||
public function unserialize($subscription): void
|
||||
{
|
||||
$data = json_decode($subscription, true);
|
||||
$data = \Safe\json_decode($subscription, true);
|
||||
|
||||
$this->operationName = $data['operation_name'];
|
||||
$this->channel = $data['channel'];
|
||||
$this->topic = $data['topic'];
|
||||
|
||||
/**
|
||||
* We know the type since it is set during construction and serialized.
|
||||
*
|
||||
* @var \GraphQL\Language\AST\DocumentNode $documentNode
|
||||
*/
|
||||
$documentNode = AST::fromArray(
|
||||
unserialize($data['query'])
|
||||
);
|
||||
$this->query = $documentNode;
|
||||
$this->fieldName = $data['field_name'];
|
||||
$this->args = $data['args'];
|
||||
$this->variables = $data['variables'];
|
||||
$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()
|
||||
public function serialize(): string
|
||||
{
|
||||
return json_encode([
|
||||
'operation_name' => $this->operationName,
|
||||
return \Safe\json_encode([
|
||||
'channel' => $this->channel,
|
||||
'args' => $this->args,
|
||||
'context' => $this->contextSerializer()->serialize($this->context),
|
||||
'topic' => $this->topic,
|
||||
'query' => serialize(
|
||||
AST::toArray($this->query)
|
||||
),
|
||||
'field_name' => $this->fieldName,
|
||||
'args' => $this->args,
|
||||
'variables' => $this->variables,
|
||||
'context' => $this->contextSerializer()->serialize($this->context),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set root data.
|
||||
*
|
||||
* @param mixed $root
|
||||
* @return $this
|
||||
* @deprecated set the attribute directly
|
||||
*/
|
||||
public function setRoot($root): self
|
||||
{
|
||||
@@ -132,17 +166,12 @@ class Subscriber implements Serializable
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
|
||||
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;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SubscriptionBroadcaster implements BroadcastsSubscriptions
|
||||
{
|
||||
@@ -22,17 +22,17 @@ class SubscriptionBroadcaster implements BroadcastsSubscriptions
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\AuthorizesSubscriptions
|
||||
*/
|
||||
protected $auth;
|
||||
protected $subscriptionAuthorizer;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\StorageManager
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions
|
||||
*/
|
||||
protected $storage;
|
||||
protected $subscriptionStorage;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator
|
||||
*/
|
||||
protected $iterator;
|
||||
protected $subscriptionIterator;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\BroadcastManager
|
||||
@@ -40,96 +40,67 @@ class SubscriptionBroadcaster implements BroadcastsSubscriptions
|
||||
protected $broadcastManager;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
* @var \Illuminate\Contracts\Bus\Dispatcher
|
||||
*/
|
||||
protected $eventsDispatcher;
|
||||
protected $busDispatcher;
|
||||
|
||||
/**
|
||||
* @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,
|
||||
AuthorizesSubscriptions $subscriptionAuthorizer,
|
||||
StoresSubscriptions $subscriptionStorage,
|
||||
SubscriptionIterator $subscriptionIterator,
|
||||
BroadcastManager $broadcastManager,
|
||||
EventsDispatcher $eventsDispatcher
|
||||
BusDispatcher $busDispatcher
|
||||
) {
|
||||
$this->graphQL = $graphQL;
|
||||
$this->auth = $auth;
|
||||
$this->storage = $storage;
|
||||
$this->iterator = $iterator;
|
||||
$this->subscriptionAuthorizer = $subscriptionAuthorizer;
|
||||
$this->subscriptionStorage = $subscriptionStorage;
|
||||
$this->subscriptionIterator = $subscriptionIterator;
|
||||
$this->broadcastManager = $broadcastManager;
|
||||
$this->eventsDispatcher = $eventsDispatcher;
|
||||
$this->busDispatcher = $busDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
);
|
||||
$broadcastSubscriptionJob = new BroadcastSubscriptionJob($subscription, $fieldName, $root);
|
||||
$broadcastSubscriptionJob->onQueue(config('lighthouse.subscriptions.broadcasts_queue_name'));
|
||||
|
||||
$this->busDispatcher->dispatch($broadcastSubscriptionJob);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
$subscribers = $this->subscriptionStorage
|
||||
->subscribersByTopic($topic)
|
||||
->filter(function (Subscriber $subscriber) use ($subscription, $root): bool {
|
||||
return $subscription->filter($subscriber, $root);
|
||||
});
|
||||
|
||||
$this->iterator->process(
|
||||
$this->subscriptionIterator->process(
|
||||
$subscribers,
|
||||
function (Subscriber $subscriber) use ($root): void {
|
||||
$data = $this->graphQL->executeQuery(
|
||||
$subscriber->root = $root;
|
||||
|
||||
$executionResult = $this->graphQL->executeQuery(
|
||||
$subscriber->query,
|
||||
$subscriber->context,
|
||||
$subscriber->args,
|
||||
$subscriber->setRoot($root),
|
||||
$subscriber->operationName
|
||||
$subscriber->variables,
|
||||
$subscriber
|
||||
);
|
||||
|
||||
$this->broadcastManager->broadcast(
|
||||
$subscriber,
|
||||
$data->jsonSerialize()
|
||||
$this->graphQL->serializable($executionResult)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize the subscription.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function authorize(Request $request): Response
|
||||
{
|
||||
return $this->auth->authorize($request)
|
||||
return $this->subscriptionAuthorizer->authorize($request)
|
||||
? $this->broadcastManager->authorized($request)
|
||||
: $this->broadcastManager->unauthorized($request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SubscriptionController
|
||||
{
|
||||
public function authorize(Request $request, BroadcastsSubscriptions $broadcaster): Response
|
||||
{
|
||||
return $broadcaster->authorize($request);
|
||||
}
|
||||
|
||||
public function webhook(Request $request, BroadcastManager $broadcastManager): Response
|
||||
{
|
||||
return $broadcastManager->hook($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Nuwave\Lighthouse\Subscriptions;
|
||||
|
||||
use Illuminate\Auth\GuardHelpers;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use RuntimeException;
|
||||
|
||||
class SubscriptionGuard implements Guard
|
||||
{
|
||||
use GuardHelpers;
|
||||
|
||||
public const GUARD_NAME = 'lighthouse_subscriptions';
|
||||
|
||||
/**
|
||||
* The currently authenticated user.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Auth\Authenticatable|null
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
public function user(): ?Authenticatable
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->user = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $credentials
|
||||
*/
|
||||
public function validate(array $credentials = []): bool
|
||||
{
|
||||
throw new RuntimeException('The Lighthouse subscription guard cannot be used for credential based authentication.');
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,21 @@
|
||||
|
||||
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 GraphQL\Type\Definition\ObjectType;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Illuminate\Support\Collection;
|
||||
use Nuwave\Lighthouse\Events\BuildExtensionsResponse;
|
||||
use Nuwave\Lighthouse\Events\StartExecution;
|
||||
use Nuwave\Lighthouse\Exceptions\DefinitionException;
|
||||
use Nuwave\Lighthouse\Execution\ExtensionsResponse;
|
||||
use Nuwave\Lighthouse\Schema\SchemaBuilder;
|
||||
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
|
||||
use Nuwave\Lighthouse\Schema\Types\NotFoundSubscription;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\ContextSerializer;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Support\Utils;
|
||||
|
||||
class SubscriptionRegistry
|
||||
{
|
||||
@@ -22,47 +26,45 @@ class SubscriptionRegistry
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\StorageManager
|
||||
* @var \Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @var \Nuwave\Lighthouse\GraphQL
|
||||
* @var \Nuwave\Lighthouse\Schema\SchemaBuilder
|
||||
*/
|
||||
protected $graphQL;
|
||||
protected $schemaBuilder;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
protected $configRepository;
|
||||
|
||||
/**
|
||||
* A map from operation names to channel names.
|
||||
*
|
||||
* @var string[]
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $subscribers = [];
|
||||
|
||||
/**
|
||||
* Active subscription fields of the schema.
|
||||
*
|
||||
* @var \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription[]
|
||||
* @var array<string, \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)
|
||||
public function __construct(ContextSerializer $serializer, StoresSubscriptions $storage, SchemaBuilder $schemaBuilder, ConfigRepository $configRepository)
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
$this->storage = $storage;
|
||||
$this->graphQL = $graphQL;
|
||||
$this->schemaBuilder = $schemaBuilder;
|
||||
$this->configRepository = $configRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subscription to registry.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription $subscription
|
||||
* @param string $field
|
||||
* @return $this
|
||||
*/
|
||||
public function register(GraphQLSubscription $subscription, string $field): self
|
||||
@@ -74,33 +76,44 @@ class SubscriptionRegistry
|
||||
|
||||
/**
|
||||
* Check if subscription is registered.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->subscriptions[$key]);
|
||||
if (isset($this->subscriptions[$key])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->subscriptionType()->hasField($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription keys.
|
||||
*
|
||||
* @return string[]
|
||||
* @return array<string>
|
||||
*
|
||||
* @deprecated Use the `GraphQL\Type\Schema::subscriptionType()->getFieldNames()` method directly.
|
||||
*/
|
||||
public function keys(): array
|
||||
{
|
||||
return array_keys($this->subscriptions);
|
||||
return $this->subscriptionType()->getFieldNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance of subscription.
|
||||
*
|
||||
* @param string $key
|
||||
* @return \Nuwave\Lighthouse\Schema\Types\GraphQLSubscription
|
||||
*/
|
||||
public function subscription(string $key): GraphQLSubscription
|
||||
{
|
||||
if (! isset($this->subscriptions[$key])) {
|
||||
/**
|
||||
* Loading the field has the side effect of triggering a call to.
|
||||
* @see \Nuwave\Lighthouse\Support\Contracts\ProvidesSubscriptionResolver::provideSubscriptionResolver()
|
||||
* which is then expected to call @see register().
|
||||
*
|
||||
* TODO make this more explicit and safe
|
||||
*/
|
||||
$this->subscriptionType()->getField($key);
|
||||
}
|
||||
|
||||
return $this->subscriptions[$key];
|
||||
}
|
||||
|
||||
@@ -108,16 +121,12 @@ class SubscriptionRegistry
|
||||
* Add subscription to registry.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @param string $channel
|
||||
* @return $this
|
||||
*/
|
||||
public function subscriber(Subscriber $subscriber, string $channel): self
|
||||
public function subscriber(Subscriber $subscriber, string $topic): self
|
||||
{
|
||||
if ($subscriber->channel) {
|
||||
$this->storage->storeSubscriber($subscriber, $channel);
|
||||
}
|
||||
|
||||
$this->subscribers[$subscriber->operationName] = $subscriber->channel;
|
||||
$this->storage->storeSubscriber($subscriber, $topic);
|
||||
$this->subscribers[$subscriber->fieldName] = $subscriber->channel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -126,18 +135,14 @@ class SubscriptionRegistry
|
||||
* Get registered subscriptions.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @return \Illuminate\Support\Collection<\Nuwave\Lighthouse\Schema\Types\GraphQLSubscription>
|
||||
*/
|
||||
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(
|
||||
Utils::instanceofMatcher(OperationDefinitionNode::class)
|
||||
)
|
||||
->filter(function (OperationDefinitionNode $node): bool {
|
||||
return $node->operation === 'subscription';
|
||||
})
|
||||
@@ -148,39 +153,65 @@ class SubscriptionRegistry
|
||||
})
|
||||
->all();
|
||||
})
|
||||
->map(function ($subscriptionField): GraphQLSubscription {
|
||||
return Arr::get(
|
||||
$this->subscriptions,
|
||||
$subscriptionField,
|
||||
new NotFoundSubscription
|
||||
);
|
||||
->map(function (string $subscriptionField): GraphQLSubscription {
|
||||
if ($this->has($subscriptionField)) {
|
||||
return $this->subscription($subscriptionField);
|
||||
}
|
||||
|
||||
return 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
|
||||
public function handleBuildExtensionsResponse(BuildExtensionsResponse $buildExtensionsResponse): ?ExtensionsResponse
|
||||
{
|
||||
return new ExtensionsResponse(
|
||||
'lighthouse_subscriptions',
|
||||
[
|
||||
'version' => 1,
|
||||
'channels' => $this->subscribers,
|
||||
]
|
||||
);
|
||||
$subscriptionsConfig = $this->configRepository->get('lighthouse.subscriptions');
|
||||
|
||||
$channel = count($this->subscribers) > 0
|
||||
? reset($this->subscribers)
|
||||
: null;
|
||||
|
||||
if ($channel === null && ($subscriptionsConfig['exclude_empty'] ?? false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$version = $subscriptionsConfig['version'] ?? 1;
|
||||
switch ((int) $version) {
|
||||
case 1:
|
||||
$content = [
|
||||
'version' => 1,
|
||||
'channel' => $channel,
|
||||
'channels' => $this->subscribers,
|
||||
];
|
||||
break;
|
||||
case 2:
|
||||
$content = [
|
||||
'version' => 2,
|
||||
'channel' => $channel,
|
||||
];
|
||||
break;
|
||||
default:
|
||||
throw new DefinitionException("Expected lighthouse.subscriptions.version to be 1 or 2, got: {$version}");
|
||||
}
|
||||
|
||||
return new ExtensionsResponse('lighthouse_subscriptions', $content);
|
||||
}
|
||||
|
||||
protected function subscriptionType(): ObjectType
|
||||
{
|
||||
$subscriptionType = $this->schemaBuilder->schema()->getSubscriptionType();
|
||||
|
||||
if ($subscriptionType === null) {
|
||||
throw new DefinitionException('Schema is missing subscription root type.');
|
||||
}
|
||||
|
||||
return $subscriptionType;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-17
@@ -3,17 +3,17 @@
|
||||
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 Illuminate\Support\Str;
|
||||
use Nuwave\Lighthouse\Exceptions\DefinitionException;
|
||||
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
|
||||
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\Schema\Values\FieldValue;
|
||||
use Nuwave\Lighthouse\Subscriptions\Directives\SubscriptionDirective;
|
||||
use Nuwave\Lighthouse\Subscriptions\Exceptions\UnauthorizedSubscriber;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
use Nuwave\Lighthouse\Support\Contracts\ProvidesSubscriptionResolver;
|
||||
use Nuwave\Lighthouse\Support\Utils;
|
||||
|
||||
class SubscriptionResolverProvider implements ProvidesSubscriptionResolver
|
||||
{
|
||||
@@ -22,12 +22,6 @@ class SubscriptionResolverProvider implements ProvidesSubscriptionResolver
|
||||
*/
|
||||
protected $subscriptionRegistry;
|
||||
|
||||
/**
|
||||
* ResolverProvider constructor.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Subscriptions\SubscriptionRegistry $subscriptionRegistry
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(SubscriptionRegistry $subscriptionRegistry)
|
||||
{
|
||||
$this->subscriptionRegistry = $subscriptionRegistry;
|
||||
@@ -36,16 +30,16 @@ class SubscriptionResolverProvider implements ProvidesSubscriptionResolver
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return \Closure(mixed, array<string, mixed>, \Nuwave\Lighthouse\Support\Contracts\GraphQLContext, \GraphQL\Type\Definition\ResolveInfo): mixed
|
||||
*/
|
||||
public function provideSubscriptionResolver(FieldValue $fieldValue): Closure
|
||||
{
|
||||
$fieldName = $fieldValue->getFieldName();
|
||||
|
||||
if ($directive = ASTHelper::directiveDefinition($fieldValue->getField(), SubscriptionDirective::NAME)) {
|
||||
$directive = ASTHelper::directiveDefinition($fieldValue->getField(), SubscriptionDirective::NAME);
|
||||
if ($directive !== null) {
|
||||
$className = ASTHelper::directiveArgValue($directive, 'class');
|
||||
} else {
|
||||
$className = Str::studly($fieldName);
|
||||
@@ -96,6 +90,8 @@ class SubscriptionResolverProvider implements ProvidesSubscriptionResolver
|
||||
$subscriber,
|
||||
$subscription->encodeTopic($subscriber, $fieldName)
|
||||
);
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
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
|
||||
* @param \Illuminate\Contracts\Routing\Registrar|\Laravel\Lumen\Routing\Router $router
|
||||
*/
|
||||
public function pusher($router): void
|
||||
{
|
||||
@@ -20,8 +17,19 @@ class SubscriptionRouter
|
||||
]);
|
||||
|
||||
$router->post('graphql/subscriptions/webhook', [
|
||||
'as' => 'lighthouse.subscriptions.auth',
|
||||
'as' => 'lighthouse.subscriptions.webhook',
|
||||
'uses' => SubscriptionController::class.'@webhook',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Contracts\Routing\Registrar|\Laravel\Lumen\Routing\Router $router
|
||||
*/
|
||||
public function echoRoutes($router): void
|
||||
{
|
||||
$router->post('graphql/subscriptions/auth', [
|
||||
'as' => 'lighthouse.subscriptions.auth',
|
||||
'uses' => SubscriptionController::class.'@authorize',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-46
@@ -2,37 +2,54 @@
|
||||
|
||||
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\Auth\AuthManager;
|
||||
use Illuminate\Container\Container;
|
||||
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 Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use Nuwave\Lighthouse\Events\BuildExtensionsResponse;
|
||||
use Nuwave\Lighthouse\Events\RegisterDirectiveNamespaces;
|
||||
use Nuwave\Lighthouse\Events\StartExecution;
|
||||
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\ContextSerializer;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\StoresSubscriptions;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionExceptionHandler;
|
||||
use Nuwave\Lighthouse\Subscriptions\Contracts\SubscriptionIterator;
|
||||
use Nuwave\Lighthouse\Subscriptions\Iterators\AuthenticatingSyncIterator;
|
||||
use Nuwave\Lighthouse\Subscriptions\Iterators\SyncIterator;
|
||||
use Nuwave\Lighthouse\Subscriptions\Storage\CacheStorageManager;
|
||||
use Nuwave\Lighthouse\Subscriptions\Storage\RedisStorageManager;
|
||||
use Nuwave\Lighthouse\Support\Contracts\ProvidesSubscriptionResolver;
|
||||
|
||||
class SubscriptionServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $eventsDispatcher
|
||||
* @param \Illuminate\Contracts\Config\Repository $configRepository
|
||||
* @return void
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(BroadcastManager::class);
|
||||
$this->app->singleton(SubscriptionRegistry::class);
|
||||
$this->app->singleton(StoresSubscriptions::class, static function (Container $app): StoresSubscriptions {
|
||||
/** @var \Illuminate\Contracts\Config\Repository $configRepository */
|
||||
$configRepository = $app->make(ConfigRepository::class);
|
||||
switch ($configRepository->get('lighthouse.subscriptions.storage')) {
|
||||
case 'redis':
|
||||
return $app->make(RedisStorageManager::class);
|
||||
default:
|
||||
return $app->make(CacheStorageManager::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);
|
||||
}
|
||||
|
||||
public function boot(EventsDispatcher $eventsDispatcher, ConfigRepository $configRepository): void
|
||||
{
|
||||
$eventsDispatcher->listen(
|
||||
BroadcastSubscriptionEvent::class,
|
||||
BroadcastSubscriptionListener::class
|
||||
);
|
||||
|
||||
$eventsDispatcher->listen(
|
||||
StartExecution::class,
|
||||
SubscriptionRegistry::class.'@handleStartExecution'
|
||||
@@ -43,36 +60,43 @@ class SubscriptionServiceProvider extends ServiceProvider
|
||||
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');
|
||||
$eventsDispatcher->listen(
|
||||
RegisterDirectiveNamespaces::class,
|
||||
static function (): string {
|
||||
return __NAMESPACE__.'\\Directives';
|
||||
}
|
||||
);
|
||||
|
||||
call_user_func(
|
||||
[$this->app->make($router), $method],
|
||||
$this->app->make('router')
|
||||
);
|
||||
$this->registerBroadcasterRoutes($configRepository);
|
||||
|
||||
// If authentication is used, we can log in subscribers when broadcasting an update
|
||||
if ($this->app->bound(AuthManager::class)) {
|
||||
config([
|
||||
'auth.guards.'.SubscriptionGuard::GUARD_NAME => [
|
||||
'driver' => SubscriptionGuard::GUARD_NAME,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->app->bind(SubscriptionIterator::class, AuthenticatingSyncIterator::class);
|
||||
|
||||
$this->app->make(AuthManager::class)->extend(SubscriptionGuard::GUARD_NAME, static function () {
|
||||
return new SubscriptionGuard;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register subscription services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register(): void
|
||||
protected function registerBroadcasterRoutes(ConfigRepository $configRepository): void
|
||||
{
|
||||
$this->app->singleton(BroadcastManager::class);
|
||||
$this->app->singleton(SubscriptionRegistry::class);
|
||||
$this->app->singleton(StoresSubscriptions::class, StorageManager::class);
|
||||
$broadcaster = $configRepository->get('lighthouse.subscriptions.broadcaster');
|
||||
|
||||
$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);
|
||||
if ($routesMethod = $configRepository->get("lighthouse.subscriptions.broadcasters.{$broadcaster}.routes")) {
|
||||
[$routesProviderClass, $method] = Str::parseCallback($routesMethod, 'pusher');
|
||||
/** @var class-string $routesProviderClass */
|
||||
/** @var string $method */
|
||||
$routesProvider = $this->app->make($routesProviderClass);
|
||||
$router = $this->app->make('router');
|
||||
|
||||
$routesProvider->{$method}($router);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user