Git Product home page Git Product logo

laravel-mercure-broadcaster's People

Contributors

dunglas avatar mvanduijker avatar tchartron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

laravel-mercure-broadcaster's Issues

Well done

Sorry, not an issue. But wanted to drop a message saying we’ll done.

Haven’t tried yet, but reading code online.

How to use with self-signed SSL in localhost

I have setup mercure with the docker-compose. It used a self-signed certificate and I don't find how to allow Laravel to send the request with this package.

I have the following error

[previous exception] [object] (Symfony\\Component\\HttpClient\\Exception\\TransportException(code: 0): SSL certificate problem: unable to get local issuer certificate for \"https://localhost/.well-known/mercure\". at C:\\laragon\\www\\premo\\vendor\\symfony\\http-client\\Chunk\\ErrorChunk.php:56)

Here the docker-compose:

# docker-compose.yml
version: "3.7"

services:
  mercure:
    image: dunglas/mercure
    restart: unless-stopped
    environment:
      # Uncomment the following line to disable HTTPS
      # SERVER_NAME: ':1337'
      MERCURE_PUBLISHER_JWT_KEY: 'ChangeMe'
      MERCURE_SUBSCRIBER_JWT_KEY: 'ChangeMe'
      MERCURE_EXTRA_DIRECTIVES: |-
        cors_origins *
    # Comment / Uncomment - the following line to enable the development mode
    command: /usr/bin/caddy run -config /etc/caddy/Caddyfile.dev
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

Why I need SSL on localhost? Because otherwise the browser use Http/1.1 and I have already reached the connection limit. But with Http/2 the limit is a lot more (about 100).

Broken JWT builder: version bumps of mercure and php JWT dependency

Used versions for testing:
mercure: v0.10
laravel-mercure-broadcaster: v3.1.0

Possible fixes:

Error messages:

  • in Browser:
HTTP 401 unauthorized
  • mercure logs:
{"level":"info","ts":1606474124.5431101,"logger":"http.handlers.mercure","msg":"Topic selectors not matched or not provided","remote_addr":"x.x.x.x:46442","error":"unable to parse JWT: signature is invalid"}
  • php laravel logs:
trigger_error('Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes.', E_USER_DEPRECATED);

More upgrades incoming:
Upgrading docs for incoming php jwt dep:
https://github.com/lcobucci/jwt/blob/master/docs/upgrading.md

Integrate the auth middleware into the library

It feels like the JWT middleware for private channels should be integrated in the library. I'm happy to contribute a PR, but I wanted to talk about the design first. I think most of the dynamic values could be configured in broadcasting.connections.mercure, with exception of the subscription URLs. What I'd aim for here is an integration of Laravel routing, so you can pass route IDs in addition to URI patterns:

Route::middleware('mercure.auth:user.notifications,id')->get('/user/{id}', [UserController::class, 'notifications']);
Route::middleware('mercure.auth:https://my.app/users/{id}/notifications')->get('/user/{id}', [UserController::class, 'notifications']);
Route::middleware('mercure.auth:/users/{id}/notifications')->get('/user/{id}', [UserController::class, 'notifications']);

The first middleware identifier, mercure.auth:user.notifications,id, passes the route name user.notifications and the request parameter id. By checking whether a route with that name exists and fetching the parameter from the request, we should be able to build the full subscription URL on behalf of the developer automatically.

In the second case, we receive a full URL with an inline parameter ({id}). Again, we can use the route parameters to replace the parameter with the actual value from the request (I've done this once for another package and it works well).

In the third case, if the first parameter starts with a slash, we can assume it's an URI, prepend current host and scheme in front of it, then continue as for case two.

All in all, this would make it really easy to use private channels and fit well into the Laravel ecosystem. What do you think?

Question about channels

Hi, I try to implement your package on my app', I want to add channel (in channels.php, but i don't know if it's necessary)

With something like this :
Broadcast::channel('{user}.example', fn() => true);

When run composer install, Laravel throw me an error :
#message: "Call to undefined method Duijker\LaravelMercureBroadcaster\Broadcasting\Broadcasters\MercureBroadcaster::channel()" #code: 0 #file: "./vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php"

The "channel()" method refer to "Broadcast::channel()" methods in channels.php.
Are there alternative existing ? Or maybe i can't need to declare a channel, the package do not map model's attribute to change it state in real time ?

Do you have an example of your package implementation in Laravel from scratch ? Thanks

Unable to publish to mercure hub using Laravel 8 event broadcasting

Hello,

This is very likely not an issue with the package but something i'm not doing right ... i've been overthinking this for some times now and would appreciate a fresh look ...

I'm not able to receive events from laravel event broadcasting, not sure why but i feel like im making things too complex. I think i'm doing it wrong.

I'm able to subscribe to event on the frontend using :

      var es = new EventSource(`http://${window.location.hostname}:3030/.well-known/mercure?topic=` + encodeURIComponent(`http://expl/profile/${this.profile.id}`), { withCredentials: true });
      es.addEventListener('message', (messageEvent) => {
          console.log(messageEvent.data);
      });

I can publish and get events datas in the browser console by using an Http request client (Insomnia, Postman, ...) but i can't get any event from laravel event broadcasting ...

I've generated my keypair following the Mercure.rocks documentation

docker-compose.yml

  mercure:
    image: dunglas/mercure
    container_name: app-mercure
    restart: unless-stopped
    environment:
      DEBUG: "debug"
      SERVER_NAME: ':80'
      # MERCURE_TRANSPORT_URL: "bolt://mercure.db"
      MERCURE_PUBLISHER_JWT_KEY: |
        -----BEGIN PUBLIC KEY-----
        M3IBgkq.......GSzhP1............dQ==
        -----END PUBLIC KEY-----
      MERCURE_PUBLISHER_JWT_ALG: RS256
      MERCURE_SUBSCRIBER_JWT_KEY: |
        -----BEGIN PUBLIC KEY-----
        M3IBgkq........hkiBA..........AQ==
        -----END PUBLIC KEY-----
      MERCURE_SUBSCRIBER_JWT_ALG: RS256
      MERCURE_EXTRA_DIRECTIVES: |-
        cors_origins http://example.web:8181
        anonymous
    ports:
      - "3030:80"
    networks:
      - app-network

.env file (inlined public keys)

BROADCAST_DRIVER=mercure
....
MERCURE_SECRET_SUB="-----BEGIN PUBLIC KEY-----\nM3IBgkq......hkiBA..........AQ==\n-----END PUBLIC KEY-----\n"
MERCURE_SECRET_PUB="-----BEGIN PUBLIC KEY-----\nM3IBgkq.......GSzhP1............dQ=\n-----END PUBLIC KEY-----\n"
MERCURE_URL=http://app-mercure/.well-known/mercure

config/broadcasting.php :

        'mercure' => [
            'driver' => 'mercure',
            'url' => env('MERCURE_URL', 'http://example.web:3030/.well-known/mercure'),
            'secret_pub' => env('MERCURE_SECRET_PUB', 'aVerySecretKey'),
            'secret_sub' => env('MERCURE_SECRET_SUB', 'aVerySecretKey'),
        ],

MercureBroadcasterAuthorizationCookie.php middleware (attached to every requests) :

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cookie;
use Lcobucci\JWT\Configuration;
// use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key\LocalFileReference;
use Lcobucci\JWT\Signer\Key\InMemory;

class MercureBroadcasterAuthorizationCookie
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        /** @var Response $response */
        $response = $next($request);

        if (!method_exists($response, 'withCookie')) {
            return $response;
        }

        if (!\Auth::check()) {
            return $response;
        }

        $user = $request->user();

        if (!$user->profile()->exists()) {
             return $response;
        }
        return $response->withCookie($this->createCookie($request->user(), $request->secure()));
    }

    private function createCookie($user, bool $secure)
    {
        $subscriptions = [
            sprintf("http://expl/profile/%d", $user->profile->id)
        ];

        $jwtConfiguration = Configuration::forAsymmetricSigner(
            new Signer\Rsa\Sha256(),
            LocalFileReference::file(__DIR__ . '/../../../.docker/mercure/subscriber.key'),
            InMemory::plainText(config('broadcasting.connections.mercure.secret_sub'))
        );
        $token = $jwtConfiguration->builder()
            ->withClaim('mercure', ['subscribe' => $subscriptions])
            ->getToken($jwtConfiguration->signer(), $jwtConfiguration->signingKey())
            ->toString();

        return Cookie::make(
            'mercureAuthorization',
            $token,
            15,
            '/.well-known/mercure', // or which path you have mercure running
            parse_url(config('app.url'), PHP_URL_HOST),
            $secure,
            true
        );
    }
}

Modified LaravelMercureBroadcasterServiceProvider (to use asymetric signer) :

<?php declare(strict_types = 1);

namespace Duijker\LaravelMercureBroadcaster;

use Duijker\LaravelMercureBroadcaster\Broadcasting\Broadcasters\MercureBroadcaster;
use Illuminate\Broadcasting\BroadcastManager;
use Illuminate\Support\ServiceProvider;
use Symfony\Component\Mercure\Publisher;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key\LocalFileReference;
use Lcobucci\JWT\Signer\Key\InMemory;

class LaravelMercureBroadcasterServiceProvider extends ServiceProvider
{
    public function boot()
    {

        $jwtConfiguration = Configuration::forAsymmetricSigner(
            new Signer\Rsa\Sha256(),
            LocalFileReference::file(__DIR__ . '/../../../../.docker/mercure/publisher.key'),
            InMemory::plainText(config('broadcasting.connections.mercure.secret_pub'))
        );
        $token = $jwtConfiguration->builder()
            ->withClaim('mercure', ['publish' => ["*"]])
            ->getToken($jwtConfiguration->signer(), $jwtConfiguration->signingKey())
            ->toString();

        $this->app
            ->make(BroadcastManager::class)
            ->extend('mercure', function ($app, array $config) use ($token) {
                return new MercureBroadcaster(
                    new Publisher(
                        $config['url'],
                        function () use ($config, $token) {
                            return (string) $token;
                        }
                    )
                );
            });
    }

    public function register()
    {
    }
}

app/Http/Kernel.php (middleware declaration) :

\App\Http\Middleware\MercureBroadcasterAuthorizationCookie::class,

Laravel event :

<?php

namespace App\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Duijker\LaravelMercureBroadcaster\Broadcasting\Channel;

use App\Models\Message;

class MessageSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;
    public $dest_id;
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Message $message, int $dest_id)
    {
        $this->message = $message;
        $this->dest_id = $dest_id;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel("http://expl/profile/".$this->dest_id, true);
    }
}

Dispatching event in a controller :

MessageSent::dispatch($message->load('profile'), $dest_id);

Thanks a lot if someone is able to help !
Thanks @dunglas @mvanduijker for your great work 🚀

Private channels and Laravel cookie encryption

Hello,

Thanks a lot for this implementation of mercure broadcaster for laravel.

I just would like to expose an "issue" I've been fighting with while trying to use this in a project.

It looks like by default, or at least when using a pre-built authentication stack, Laravel encrypt all cookies. So for private channels using the cookie middleware, the resulting cookie gets encrypted and cannot be parsed by the Mercure hub.

This resulted in this kind of log entries on the Hub:

mercure_1  | {"level":"info","ts":1645187479.2805336,"logger":"http.handlers.mercure","msg":"Subscriber unauthorized","subscriber":{"id":"urn:uuid:05fcf9f7-8564-45f3-952b-a8abaafadd54","last_event_id":"","remote_addr":"172.21.0.1:48160"},"error":"unable to parse JWT: token contains an invalid number of segments"}

After digging a bit I found out that you can exclude cookie names from Laravel encryption by adding an App/Middleware/EncryptCookies.php middleware and declare the cookies name to be left unencrypted in it.

<?php

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware
{
    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array<int, string>
     */
    protected $except = [
        'mercureAuthorization'
    ];
}

This resolved the issue for me. The client can now subscribe to the private channel.

mercure_1  | {"level":"info","ts":1645189281.517158,"logger":"http.handlers.mercure","msg":"New subscriber","subscriber":{"id":"urn:uuid:5cb3e680-d9a2-4fec-ba0b-a173736024ce","last_event_id":"","remote_addr":"172.21.0.1:48188","topic_selectors":["https://vpanel/user/05a39255-a0ab-4ecd-98dc-708ab9b61ae8/direct-messages"],"topics":["https://vpanel/user/05a39255-a0ab-4ecd-98dc-708ab9b61ae8/direct-messages"]}}

Maybe this should appear somewhere in the README ?

Kind regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.