Related Issue(s)
#88
PR Type
Feature
Changes
As I get through this implementation I'll be filling out documentation here so when it's ready for merge I can update the doc site's next
branch.
Subscriptions Overview
This implementation of subscriptions allows users to leverage Pusher as their websocket server (removing the need of supporting your own websocket server). Most of the credit should be given to the Ruby implementation as they provided a great overview of how the backend should work.
Requirements
Install the Pusher PHP Server package
composer require pusher/pusher-php-server
Enable the extension in the lighthouse.php config file
'extensions' => [
\Nuwave\Lighthouse\Schema\Extensions\SubscriptionExtension::class,
],
Basic Setup
TODO: Add description here...
type Mutation {
updatePost(input: UpdatePostInput!): Post
# This will pipe the Post returned from this mutation to the
# PostUpdatedSubscription resolve function
@broadcast(subscription: "postUpdated")
}
type Subscription {
postUpdated(author: ID): Post
@subscription(class: "App\\GraphQL\\Subscriptions\\PostUpdatedSubscription")
}
Subscription Class
TODO: Go through example showing all class methods...
TODO: Create example that creates a custom topic name to help filter listeners...
namespace App\GraphQL\Subscriptions;
use Illuminate\Http\Request;
use Nuwave\Lighthouse\Schema\Subscriptions\Subscriber;
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
class PostUpdatedSubscription extends GraphQLSubscription
{
public function authorize(Subscriber $subscriber, Request $request)
{
$user = $subscriber->context->user;
return $user->hasPermission('some-permission');
}
public function filter(Subscriber $subscriber, $root)
{
$user = $subscriber->context->user;
return $root->updated_by !== $user->id;
}
}
Firing a subscription via code
Using an event listener
namespace App\Listeners\Post;
use App\Http\GraphQL\Subscriptions\PostUpdatedSubscription;
use Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions;
class BroadcastPostUpdated
{
protected $broadcaster;
protected $subscription;
public function __construct(
BroadcastsSubscriptions $broadcaster,
PostUpdatedSubscription $subscription
) {
$this->broadcaster = $broadcaster;
$this->subscription = $subscription;
}
public function handle(PostUpdatedEvent $event)
{
$this->broadcaster->broadcast(
$this->subscription,
'postUpdated',
$event->post
);
}
}
Apollo Link
import { ApolloLink, Observable } from 'apollo-link';
class PusherLink extends ApolloLink {
constructor(options) {
super();
this.pusher = options.pusher;
}
request(operation, forward) {
return new Observable(observer => {
forward(operation).subscribe({
next: data => {
const subscriptionChannel = this._getChannel(
data,
operation
);
if (subscriptionChannel) {
this._createSubscription(subscriptionChannel, observer);
} else {
observer.next(data);
observer.complete();
}
},
});
});
}
_getChannel(data, operation) {
return !!data.extensions
&& !!data.extensions.lighthouse_subscriptions
&& !!data.extensions.lighthouse_subscriptions.channels
? data.extensions.lighthouse_subscriptions.channels[operation.operationName]
: null;
}
_createSubscription(subscriptionChannel, observer) {
const pusherChannel = this.pusher.subscribe(subscriptionChannel);
pusherChannel.bind('lighthouse-subscription', payload => {
if (!payload.more) {
this.pusher.unsubscribe(subscriptionChannel);
observer.complete();
}
const result = payload.result;
if (result) {
observer.next(result);
}
});
}
}
export default PusherLink;
Breaking changes
None (can be disabled by excluding the extension in the config)
TODO Items
- [x] Move namespace up a level
- [x] Create queue option to push broadcaster to background
- [x] Add Tests
- [x] Create Docs