wip
This commit is contained in:
parent
b46cfadaa2
commit
f62ac8fd56
|
|
@ -4,3 +4,4 @@ vendor
|
||||||
coverage
|
coverage
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
.idea/
|
.idea/
|
||||||
|
database.sqlite
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BeyondCode\LaravelWebSockets\Contracts;
|
||||||
|
|
||||||
|
use BeyondCode\LaravelWebSockets\PubSub\Broadcasters\RedisPusherBroadcaster;
|
||||||
|
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
||||||
|
use Pusher\Pusher;
|
||||||
|
|
||||||
|
trait PushesToPusher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the right Pusher broadcaster for the used driver.
|
||||||
|
*
|
||||||
|
* @param array $app
|
||||||
|
* @return \Illuminate\Broadcasting\Broadcasters\Broadcaster
|
||||||
|
*/
|
||||||
|
public function getPusherBroadcaster(array $app)
|
||||||
|
{
|
||||||
|
if (config('websockets.replication.driver') === 'redis') {
|
||||||
|
return new RedisPusherBroadcaster(
|
||||||
|
new Pusher($app['key'], $app['secret'], $app['id'], config('broadcasting.connections.websockets.options', [])),
|
||||||
|
$app['id'],
|
||||||
|
app('redis'),
|
||||||
|
config('broadcasting.connections.websockets.connection', null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PusherBroadcaster(
|
||||||
|
new Pusher($app['key'], $app['secret'], $app['id'], config('broadcasting.connections.pusher.options', [])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers;
|
namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers;
|
||||||
|
|
||||||
use BeyondCode\LaravelWebSockets\Apps\App;
|
use BeyondCode\LaravelWebSockets\Apps\App;
|
||||||
|
use BeyondCode\LaravelWebSockets\Contracts\PushesToPusher;
|
||||||
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Pusher\Pusher;
|
use Pusher\Pusher;
|
||||||
|
|
||||||
class AuthenticateDashboard
|
class AuthenticateDashboard
|
||||||
{
|
{
|
||||||
|
use PushesToPusher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the app by using the header
|
* Find the app by using the header
|
||||||
* and then reconstruct the PusherBroadcaster
|
* and then reconstruct the PusherBroadcaster
|
||||||
|
|
@ -21,12 +24,11 @@ class AuthenticateDashboard
|
||||||
{
|
{
|
||||||
$app = App::findById($request->header('x-app-id'));
|
$app = App::findById($request->header('x-app-id'));
|
||||||
|
|
||||||
$broadcaster = new PusherBroadcaster(new Pusher(
|
$broadcaster = $this->getPusherBroadcaster([
|
||||||
$app->key,
|
'key' => $app->key,
|
||||||
$app->secret,
|
'secret' => $app->secret,
|
||||||
$app->id,
|
'id' =>$app->id,
|
||||||
[]
|
]);
|
||||||
));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Since the dashboard itself is already secured by the
|
* Since the dashboard itself is already secured by the
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers;
|
namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers;
|
||||||
|
|
||||||
use BeyondCode\LaravelWebSockets\Statistics\Rules\AppId;
|
use BeyondCode\LaravelWebSockets\Statistics\Rules\AppId;
|
||||||
|
use BeyondCode\LaravelWebSockets\Contracts\PushesToPusher;
|
||||||
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Pusher\Pusher;
|
use Pusher\Pusher;
|
||||||
|
|
||||||
class SendMessage
|
class SendMessage
|
||||||
{
|
{
|
||||||
|
use PushesToPusher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the message to the requested channel.
|
* Send the message to the requested channel.
|
||||||
*
|
*
|
||||||
|
|
@ -17,7 +20,7 @@ class SendMessage
|
||||||
*/
|
*/
|
||||||
public function __invoke(Request $request)
|
public function __invoke(Request $request)
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$request->validate([
|
||||||
'appId' => ['required', new AppId],
|
'appId' => ['required', new AppId],
|
||||||
'key' => 'required|string',
|
'key' => 'required|string',
|
||||||
'secret' => 'required|string',
|
'secret' => 'required|string',
|
||||||
|
|
@ -26,30 +29,18 @@ class SendMessage
|
||||||
'data' => 'required|json',
|
'data' => 'required|json',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->getPusherBroadcaster($validated)->broadcast(
|
$broadcaster = $this->getPusherBroadcaster([
|
||||||
[$validated['channel']],
|
'key' => $request->key,
|
||||||
$validated['event'],
|
'secret' => $request->secret,
|
||||||
json_decode($validated['data'], true)
|
'id' => $request->appId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$broadcaster->broadcast(
|
||||||
|
[$request->channel],
|
||||||
|
$request->event,
|
||||||
|
json_decode($request->data, true)
|
||||||
);
|
);
|
||||||
|
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the pusher broadcaster for the current request.
|
|
||||||
*
|
|
||||||
* @param array $validated
|
|
||||||
* @return \Illuminate\Broadcasting\Broadcasters\PusherBroadcaster
|
|
||||||
*/
|
|
||||||
protected function getPusherBroadcaster(array $validated): PusherBroadcaster
|
|
||||||
{
|
|
||||||
$pusher = new Pusher(
|
|
||||||
$validated['key'],
|
|
||||||
$validated['secret'],
|
|
||||||
$validated['appId'],
|
|
||||||
config('broadcasting.connections.pusher.options', [])
|
|
||||||
);
|
|
||||||
|
|
||||||
return new PusherBroadcaster($pusher);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,8 +125,8 @@ class WebSocketsServiceProvider extends ServiceProvider
|
||||||
], function () {
|
], function () {
|
||||||
Route::get('/', ShowDashboard::class)->name('dashboard');
|
Route::get('/', ShowDashboard::class)->name('dashboard');
|
||||||
Route::get('/api/{appId}/statistics', [DashboardApiController::class, 'getStatistics'])->name('statistics');
|
Route::get('/api/{appId}/statistics', [DashboardApiController::class, 'getStatistics'])->name('statistics');
|
||||||
Route::post('auth', AuthenticateDashboard::class)->name('auth');
|
Route::post('/auth', AuthenticateDashboard::class)->name('auth');
|
||||||
Route::post('event', SendMessage::class)->name('send');
|
Route::post('/event', SendMessage::class)->name('send');
|
||||||
});
|
});
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
@ -140,7 +140,7 @@ class WebSocketsServiceProvider extends ServiceProvider
|
||||||
protected function registerDashboardGate()
|
protected function registerDashboardGate()
|
||||||
{
|
{
|
||||||
Gate::define('viewWebSocketsDashboard', function ($user = null) {
|
Gate::define('viewWebSocketsDashboard', function ($user = null) {
|
||||||
return $this->app->environment(['local', 'testing']);
|
return $this->app->environment('local');
|
||||||
});
|
});
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BeyondCode\LaravelWebSockets\Tests\Dashboard;
|
||||||
|
|
||||||
|
use BeyondCode\LaravelWebSockets\Tests\TestCase;
|
||||||
|
use BeyondCode\LaravelWebSockets\Tests\Models\User;
|
||||||
|
use BeyondCode\LaravelWebSockets\Tests\Mocks\Message;
|
||||||
|
|
||||||
|
class AuthTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @test */
|
||||||
|
public function can_authenticate_dashboard_over_channel()
|
||||||
|
{
|
||||||
|
$connection = $this->getConnectedWebSocketConnection(['test-channel']);
|
||||||
|
|
||||||
|
$this->pusherServer->onOpen($connection);
|
||||||
|
|
||||||
|
$this->actingAs(factory(User::class)->create())
|
||||||
|
->json('POST', route('laravel-websockets.auth'), [
|
||||||
|
'socket_id' => $connection->socketId,
|
||||||
|
'channel_name' => 'test-channel',
|
||||||
|
], ['x-app-id' => '1234'])
|
||||||
|
->seeJsonStructure([
|
||||||
|
'auth',
|
||||||
|
'channel_data',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function can_authenticate_dashboard_over_private_channel()
|
||||||
|
{
|
||||||
|
$connection = $this->getWebSocketConnection();
|
||||||
|
|
||||||
|
$this->pusherServer->onOpen($connection);
|
||||||
|
|
||||||
|
$signature = "{$connection->socketId}:private-channel";
|
||||||
|
|
||||||
|
$hashedAppSecret = hash_hmac('sha256', $signature, $connection->app->secret);
|
||||||
|
|
||||||
|
$message = new Message([
|
||||||
|
'event' => 'pusher:subscribe',
|
||||||
|
'data' => [
|
||||||
|
'auth' => "{$connection->app->key}:{$hashedAppSecret}",
|
||||||
|
'channel' => 'private-channel',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->pusherServer->onMessage($connection, $message);
|
||||||
|
|
||||||
|
$connection->assertSentEvent('pusher_internal:subscription_succeeded', [
|
||||||
|
'channel' => 'private-channel',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs(factory(User::class)->create())
|
||||||
|
->json('POST', route('laravel-websockets.auth'), [
|
||||||
|
'socket_id' => $connection->socketId,
|
||||||
|
'channel_name' => 'private-test-channel',
|
||||||
|
], ['x-app-id' => '1234'])
|
||||||
|
->seeJsonStructure([
|
||||||
|
'auth',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function can_authenticate_dashboard_over_presence_channel()
|
||||||
|
{
|
||||||
|
$connection = $this->getWebSocketConnection();
|
||||||
|
|
||||||
|
$this->pusherServer->onOpen($connection);
|
||||||
|
|
||||||
|
$channelData = [
|
||||||
|
'user_id' => 1,
|
||||||
|
'user_info' => [
|
||||||
|
'name' => 'Marcel',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$signature = "{$connection->socketId}:presence-channel:".json_encode($channelData);
|
||||||
|
|
||||||
|
$message = new Message([
|
||||||
|
'event' => 'pusher:subscribe',
|
||||||
|
'data' => [
|
||||||
|
'auth' => $connection->app->key.':'.hash_hmac('sha256', $signature, $connection->app->secret),
|
||||||
|
'channel' => 'presence-channel',
|
||||||
|
'channel_data' => json_encode($channelData),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->pusherServer->onMessage($connection, $message);
|
||||||
|
|
||||||
|
$this->actingAs(factory(User::class)->create())
|
||||||
|
->json('POST', route('laravel-websockets.auth'), [
|
||||||
|
'socket_id' => $connection->socketId,
|
||||||
|
'channel_name' => 'presence-channel',
|
||||||
|
], ['x-app-id' => '1234'])
|
||||||
|
->seeJsonStructure([
|
||||||
|
'auth',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,14 +3,13 @@
|
||||||
namespace BeyondCode\LaravelWebSockets\Tests\Dashboard;
|
namespace BeyondCode\LaravelWebSockets\Tests\Dashboard;
|
||||||
|
|
||||||
use BeyondCode\LaravelWebSockets\Tests\TestCase;
|
use BeyondCode\LaravelWebSockets\Tests\TestCase;
|
||||||
|
use BeyondCode\LaravelWebSockets\Tests\Models\User;
|
||||||
|
|
||||||
class DashboardTest extends TestCase
|
class DashboardTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @test */
|
/** @test */
|
||||||
public function cant_see_dashboard_without_authorization()
|
public function cant_see_dashboard_without_authorization()
|
||||||
{
|
{
|
||||||
config(['app.env' => 'production']);
|
|
||||||
|
|
||||||
$this->get(route('laravel-websockets.dashboard'))
|
$this->get(route('laravel-websockets.dashboard'))
|
||||||
->assertResponseStatus(403);
|
->assertResponseStatus(403);
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +17,8 @@ class DashboardTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function can_see_dashboard()
|
public function can_see_dashboard()
|
||||||
{
|
{
|
||||||
$this->get(route('laravel-websockets.dashboard'))
|
$this->actingAs(factory(User::class)->create())
|
||||||
|
->get(route('laravel-websockets.dashboard'))
|
||||||
->assertResponseOk()
|
->assertResponseOk()
|
||||||
->see('WebSockets Dashboard');
|
->see('WebSockets Dashboard');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BeyondCode\LaravelWebSockets\Tests\Models;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
|
||||||
|
class User extends Authenticatable
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'name', 'email', 'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $hidden = [
|
||||||
|
'password', 'remember_token',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -46,6 +46,12 @@ abstract class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->resetDatabase();
|
||||||
|
|
||||||
|
$this->loadLaravelMigrations(['--database' => 'sqlite']);
|
||||||
|
|
||||||
|
$this->withFactories(__DIR__.'/database/factories');
|
||||||
|
|
||||||
$this->pusherServer = $this->app->make(config('websockets.handlers.websocket'));
|
$this->pusherServer = $this->app->make(config('websockets.handlers.websocket'));
|
||||||
|
|
||||||
$this->channelManager = $this->app->make(ChannelManager::class);
|
$this->channelManager = $this->app->make(ChannelManager::class);
|
||||||
|
|
@ -69,6 +75,7 @@ abstract class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
\BeyondCode\LaravelWebSockets\WebSocketsServiceProvider::class,
|
\BeyondCode\LaravelWebSockets\WebSocketsServiceProvider::class,
|
||||||
|
TestServiceProvider::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,6 +86,16 @@ abstract class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
$app['config']->set('app.key', 'wslxrEFGWY6GfGhvN9L3wH3KSRJQQpBD');
|
$app['config']->set('app.key', 'wslxrEFGWY6GfGhvN9L3wH3KSRJQQpBD');
|
||||||
|
|
||||||
|
$app['config']->set('auth.providers.users.model', Models\User::class);
|
||||||
|
|
||||||
|
$app['config']->set('database.default', 'sqlite');
|
||||||
|
|
||||||
|
$app['config']->set('database.connections.sqlite', [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'database' => __DIR__.'/database.sqlite',
|
||||||
|
'prefix' => '',
|
||||||
|
]);
|
||||||
|
|
||||||
$app['config']->set('websockets.apps', [
|
$app['config']->set('websockets.apps', [
|
||||||
[
|
[
|
||||||
'name' => 'Test App',
|
'name' => 'Test App',
|
||||||
|
|
@ -307,4 +324,14 @@ abstract class TestCase extends BaseTestCase
|
||||||
->make(ReplicationInterface::class)
|
->make(ReplicationInterface::class)
|
||||||
->getPublishClient();
|
->getPublishClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the database.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function resetDatabase()
|
||||||
|
{
|
||||||
|
file_put_contents(__DIR__.'/database.sqlite', null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BeyondCode\LaravelWebSockets\Tests;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class TestServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Boot the service provider.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
Gate::define('viewWebSocketsDashboard', function ($user = null) {
|
||||||
|
return ! is_null($user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the service provider.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Model Factories
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This directory should contain each of the model factory definitions for
|
||||||
|
| your application. Factories provide a convenient way to generate new
|
||||||
|
| model instances for testing / seeding your application's database.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
$factory->define(\BeyondCode\LaravelWebSockets\Tests\Models\User::class, function () {
|
||||||
|
return [
|
||||||
|
'name' => 'Name'.Str::random(5),
|
||||||
|
'email' => Str::random(5).'@gmail.com',
|
||||||
|
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
|
||||||
|
'remember_token' => Str::random(10),
|
||||||
|
];
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue