diff --git a/composer.json b/composer.json index 6580e80..4e6e03a 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "illuminate/console": "5.6.*|5.7.*", "illuminate/http": "5.6.*|5.7.*", "illuminate/routing": "5.6.*|5.7.*", - "illuminate/support": "5.6.*|5.7.*" + "illuminate/support": "5.6.*|5.7.*", + "symfony/psr-http-message-bridge": "^1.1" }, "require-dev": { "larapack/dd": "^1.0", diff --git a/src/Console/StartWebSocketServer.php b/src/Console/StartWebSocketServer.php index e90caad..acc05a9 100644 --- a/src/Console/StartWebSocketServer.php +++ b/src/Console/StartWebSocketServer.php @@ -1,6 +1,6 @@ getUri()->getQuery(), $queryParameters); + + $serverRequest = (new ServerRequest( + $request->getMethod(), + $request->getUri(), + $request->getHeaders(), + $request->getBody(), + $request->getProtocolVersion() + ))->withQueryParams($queryParameters); + + $response = $this(Request::createFromBase((new HttpFoundationFactory)->createRequest($serverRequest))); $conn->send(JsonResponse::create($response)->send()); $conn->close(); @@ -56,5 +70,5 @@ abstract class EchoController implements HttpServerInterface // } - abstract public function __invoke($request); + abstract public function __invoke(Request $request); } \ No newline at end of file diff --git a/src/LaravelEcho/Http/Controllers/EventController.php b/src/LaravelEcho/Http/Controllers/EventController.php new file mode 100644 index 0000000..c3c9d54 --- /dev/null +++ b/src/LaravelEcho/Http/Controllers/EventController.php @@ -0,0 +1,14 @@ +json()->all(); + } +} \ No newline at end of file diff --git a/src/LaravelEcho/Http/Controllers/StatusController.php b/src/LaravelEcho/Http/Controllers/StatusController.php index 1729705..c487006 100644 --- a/src/LaravelEcho/Http/Controllers/StatusController.php +++ b/src/LaravelEcho/Http/Controllers/StatusController.php @@ -2,10 +2,12 @@ namespace BeyondCode\LaravelWebSockets\LaravelEcho\Http\Controllers; +use Illuminate\Http\Request; + class StatusController extends EchoController { - public function __invoke($request) + public function __invoke(Request $request) { return [ 'subscription_count' => 10 diff --git a/src/LaravelEcho/Pusher/Channels/Channel.php b/src/LaravelEcho/Pusher/Channels/Channel.php new file mode 100644 index 0000000..4adc13e --- /dev/null +++ b/src/LaravelEcho/Pusher/Channels/Channel.php @@ -0,0 +1,21 @@ +channelId = $channelId; + } + + public function subscribe(ConnectionInterface $conn, $payload) + { + + } +} \ No newline at end of file diff --git a/src/LaravelEcho/Pusher/Channels/ChannelManager.php b/src/LaravelEcho/Pusher/Channels/ChannelManager.php new file mode 100644 index 0000000..b246e3d --- /dev/null +++ b/src/LaravelEcho/Pusher/Channels/ChannelManager.php @@ -0,0 +1,29 @@ +channels[$channelId])) { + $channelClass = $this->detectChannelClass($channelId); + $this->channels[$channelId] = new $channelClass($channelId); + } + + return $this->channels[$channelId]; + } + + protected function detectChannelClass($channelId) : string + { + if (starts_with($channelId, 'private-')) { + return PrivateChannel::class; + } elseif(starts_with($channelId, 'presence-')) { + return PresenceChannel::class; + } + return Channel::class; + } +} \ No newline at end of file diff --git a/src/LaravelEcho/Pusher/Channels/PresenceChannel.php b/src/LaravelEcho/Pusher/Channels/PresenceChannel.php new file mode 100644 index 0000000..be6bc8d --- /dev/null +++ b/src/LaravelEcho/Pusher/Channels/PresenceChannel.php @@ -0,0 +1,34 @@ +channel_data); + $this->subscriptions[$channelData->user_id] = $channelData; + + // Send the success event + $conn->send(json_encode([ + 'event' => 'pusher_internal:subscription_succeeded', + 'channel' => $this->channelId, + 'data' => json_encode($this->getChannelData()) + ])); + } + + protected function getChannelData() + { + return [ + 'presence' => [ + 'ids' => array_keys($this->subscriptions), + 'hash' => array_map(function($channelData) { return $channelData->user_info; }, $this->subscriptions), + 'count' => count($this->subscriptions) + ] + ]; + } +} \ No newline at end of file diff --git a/src/LaravelEcho/Pusher/Channels/PrivateChannel.php b/src/LaravelEcho/Pusher/Channels/PrivateChannel.php new file mode 100644 index 0000000..f358cb3 --- /dev/null +++ b/src/LaravelEcho/Pusher/Channels/PrivateChannel.php @@ -0,0 +1,8 @@ +channelManager = $channelManager; + } + + /** + * When a new connection is opened it will be passed to this method + * @param ConnectionInterface $conn The socket/connection that just connected to your application + * @throws \Exception + */ + function onOpen(ConnectionInterface $conn) + { + dump("Client connected"); + + $socketId = sprintf("%d.%d", getmypid(), random_int(1, 100000000)); + + // Store the socketId along with the connection so we can retrieve it. + $conn->socketId = $socketId; + + $conn->send($this->buildPayload('pusher:connection_established', [ + 'socket_id' => $socketId, + 'activity_timeout' => 60, + ])); + } + + public function onMessage(ConnectionInterface $conn, MessageInterface $msg) + { + $payload = json_decode($msg->getPayload()); + + dump("Received payload", $payload); + + // todo: validate payload + $event = camel_case(str_replace(':', '_', $payload->event)); + + if (method_exists($this, $event)) { + call_user_func([$this, $event], $conn, $payload->data); + } + } + + /** + * @link https://pusher.com/docs/pusher_protocol#ping-pong + * @param ConnectionInterface $conn + * @param $payload + */ + protected function pusherPing(ConnectionInterface $conn, $payload) + { + $conn->send($this->buildPayload('pusher:pong')); + } + + /** + * @link https://pusher.com/docs/pusher_protocol#pusher-subscribe + * @param ConnectionInterface $conn + * @param $payload + */ + protected function pusherSubscribe(ConnectionInterface $conn, $payload) + { + $channel = $this->channelManager->findOrCreate($payload->channel); + $channel->subscribe($conn, $payload); + } + + protected function buildPayload($event, $data = []) + { + return json_encode([ + 'event' => $event, + 'data' => json_encode($data) + ]); + } +} \ No newline at end of file diff --git a/src/LaravelWebSocketsServiceProvider.php b/src/LaravelWebSocketsServiceProvider.php index 587a877..2769ed6 100644 --- a/src/LaravelWebSocketsServiceProvider.php +++ b/src/LaravelWebSocketsServiceProvider.php @@ -3,6 +3,7 @@ namespace BeyondCode\LaravelWebSockets; use Illuminate\Support\ServiceProvider; +use BeyondCode\LaravelWebSockets\LaravelEcho\Pusher\Channels\ChannelManager; class LaravelWebSocketsServiceProvider extends ServiceProvider { @@ -24,5 +25,8 @@ class LaravelWebSocketsServiceProvider extends ServiceProvider $this->app->singleton('websockets.router', function() { return new Router(); }); + $this->app->singleton(ChannelManager::class, function() { + return new ChannelManager(); + }); } } diff --git a/src/Router.php b/src/Router.php index 0bf4fe2..6704b1b 100644 --- a/src/Router.php +++ b/src/Router.php @@ -6,6 +6,7 @@ use Ratchet\WebSocket\WsServer; use Symfony\Component\Routing\Route; use Ratchet\Http\HttpServerInterface; use Symfony\Component\Routing\RouteCollection; +use Ratchet\WebSocket\MessageComponentInterface; use BeyondCode\LaravelWebSockets\Exceptions\InvalidWebSocketController; class Router @@ -73,11 +74,12 @@ class Router */ public function echo() { - //$this->get('/', EchoWebsocketServer::class); + $this->get('/app/{appId}', LaravelEcho\WebSocket\EchoServer::class); $this->get('/apps/{appId}/status', LaravelEcho\Http\Controllers\StatusController::class); $this->get('/apps/{appId}/channels', LaravelEcho\Http\Controllers\StatusController::class); $this->get('/apps/{appId}/channels/{channelName}', LaravelEcho\Http\Controllers\StatusController::class); $this->get('/apps/{appId}/channels/{channelName}/users', LaravelEcho\Http\Controllers\StatusController::class); + $this->post('/apps/{appId}/events', LaravelEcho\Http\Controllers\EventController::class); } /** diff --git a/src/Server/WebSocketServer.php b/src/Server/WebSocketServer.php index 53158be..b510f99 100644 --- a/src/Server/WebSocketServer.php +++ b/src/Server/WebSocketServer.php @@ -1,6 +1,6 @@