Skip to content

Commit 35f2e95

Browse files
committed
Adding Vapor handler
1 parent 021580f commit 35f2e95

File tree

4 files changed

+132
-47
lines changed

4 files changed

+132
-47
lines changed

src/Commands/VaporHandle.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Georgeboot\LaravelEchoApiGateway\Commands;
4+
5+
use Bref\Context\Context;
6+
use Georgeboot\LaravelEchoApiGateway\Handler;
7+
use Illuminate\Console\Command;
8+
use Illuminate\Support\Arr;
9+
use InvalidArgumentException;
10+
11+
class VaporHandle extends Command
12+
{
13+
/**
14+
* The name and signature of the console command.
15+
*
16+
* @var string
17+
*/
18+
protected $signature = 'vapor:handle
19+
{message : The Base64 encoded message payload}';
20+
21+
/**
22+
* The console command description.
23+
*
24+
* @var string
25+
*/
26+
protected $description = 'Handle custom lambda events in Vapor';
27+
28+
protected Handler $websocketHandler;
29+
30+
public function __construct(Handler $websocketHandler)
31+
{
32+
parent::__construct();
33+
34+
$this->websocketHandler = $websocketHandler;
35+
}
36+
37+
/**
38+
* Execute the console command.
39+
*
40+
* @return int
41+
*/
42+
public function handle()
43+
{
44+
if ($this->laravel->isDownForMaintenance()) {
45+
return 0;
46+
}
47+
48+
// fake a context
49+
$context = new Context($_ENV['AWS_REQUEST_ID'] ?? 'request-1', 0, $_ENV['AWS_LAMBDA_FUNCTION_NAME'] ?? 'arn-1', $_ENV['_X_AMZN_TRACE_ID'] ?? '');
50+
51+
if (Arr::get($this->message(), 'requestContext.connectionId')) {
52+
$this->handleWebsocketEvent($this->message(), $context);
53+
}
54+
55+
return 0;
56+
}
57+
58+
protected function handleWebsocketEvent(array $event, Context $context): void
59+
{
60+
$this->websocketHandler->handle($event, $context);
61+
}
62+
63+
/**
64+
* Get the decoded message payload.
65+
*
66+
* @return array
67+
*/
68+
protected function message()
69+
{
70+
return tap(json_decode(base64_decode($this->argument('message')), true), function ($message) {
71+
if ($message === false) {
72+
throw new InvalidArgumentException('Unable to unserialize message.');
73+
}
74+
});
75+
}
76+
}

src/Handler.php

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
class Handler extends WebsocketHandler
1414
{
1515
protected ExceptionHandler $exceptionHandler;
16-
protected SubscriptionRepository $connectionRepository;
16+
protected SubscriptionRepository $subscriptionRepository;
17+
protected ConnectionRepository $connectionRepository;
1718

18-
public function __construct(ExceptionHandler $exceptionHandler, SubscriptionRepository $connectionRepository)
19+
public function __construct(ExceptionHandler $exceptionHandler, SubscriptionRepository $subscriptionRepository, ConnectionRepository $connectionRepository)
1920
{
2021
$this->exceptionHandler = $exceptionHandler;
22+
$this->subscriptionRepository = $subscriptionRepository;
2123
$this->connectionRepository = $connectionRepository;
2224
}
2325

@@ -30,22 +32,22 @@ public function handleWebsocket(WebsocketEvent $event, Context $context): HttpRe
3032
throw new \InvalidArgumentException("Event type {$event->getEventType()} has no handler implemented.");
3133
}
3234

33-
return $this->$method($event, $context);
35+
$this->$method($event, $context);
36+
37+
return new HttpResponse('OK');
3438
} catch (Throwable $throwable) {
3539
$this->exceptionHandler->report($throwable);
3640

3741
throw $throwable;
3842
}
3943
}
4044

41-
protected function handleDisconnect(WebsocketEvent $event, Context $context): HttpResponse
45+
protected function handleDisconnect(WebsocketEvent $event, Context $context): void
4246
{
43-
$this->connectionRepository->clearConnection($event->getConnectionId());
44-
45-
return new HttpResponse('OK');
47+
$this->subscriptionRepository->clearConnection($event->getConnectionId());
4648
}
4749

48-
protected function handleMessage(WebsocketEvent $event, Context $context): HttpResponse
50+
protected function handleMessage(WebsocketEvent $event, Context $context): void
4951
{
5052
$eventBody = json_decode($event->getBody(), true);
5153

@@ -56,36 +58,29 @@ protected function handleMessage(WebsocketEvent $event, Context $context): HttpR
5658
$eventType = $eventBody['event'];
5759

5860
if ($eventType === 'ping') {
59-
return $this->jsonResponse([
61+
$this->sendMessage($event, $context, [
6062
'event' => 'pong',
6163
'channel' => $eventBody['channel'] ?? null,
6264
]);
63-
}
64-
65-
if ($eventType === 'whoami') {
66-
return $this->jsonResponse([
65+
} elseif ($eventType === 'whoami') {
66+
$this->sendMessage($event, $context, [
6767
'event' => 'whoami',
6868
'data' => [
6969
'socket_id' => $event->getConnectionId(),
7070
],
7171
]);
72+
} elseif ($eventType === 'subscribe') {
73+
$this->subscribe($event, $context);
74+
} elseif ($eventType === 'unsubscribe') {
75+
$this->unsubscribe($event, $context);
76+
} else {
77+
$this->sendMessage($event, $context, [
78+
'event' => 'error',
79+
]);
7280
}
73-
74-
if ($eventType === 'subscribe') {
75-
return $this->subscribe($event, $context);
76-
}
77-
78-
if ($eventType === 'unsubscribe') {
79-
return $this->unsubscribe($event, $context);
80-
}
81-
82-
83-
return $this->jsonResponse([
84-
'event' => 'error',
85-
]);
8681
}
8782

88-
protected function subscribe(WebsocketEvent $event, Context $context): HttpResponse
83+
protected function subscribe(WebsocketEvent $event, Context $context): void
8984
{
9085
$eventBody = json_decode($event->getBody(), true);
9186

@@ -108,41 +103,43 @@ protected function subscribe(WebsocketEvent $event, Context $context): HttpRespo
108103
$signature = hash_hmac('sha256', $data, config('app.key'), false);
109104

110105
if ($signature !== $auth) {
111-
return $this->jsonResponse([
106+
$this->sendMessage($event, $context, [
112107
'event' => 'error',
113108
'channel' => $channel,
114109
'data' => [
115110
'message' => 'Invalid auth signature',
116111
],
117112
]);
113+
114+
return;
118115
}
119116
}
120117

121-
$this->connectionRepository->subscribeToChannel($event->getConnectionId(), $channel);
118+
$this->subscriptionRepository->subscribeToChannel($event->getConnectionId(), $channel);
122119

123-
return $this->jsonResponse([
120+
$this->sendMessage($event, $context, [
124121
'event' => 'subscription_succeeded',
125122
'channel' => $channel,
126123
'data' => [],
127124
]);
128125
}
129126

130-
protected function unsubscribe(WebsocketEvent $event, Context $context): HttpResponse
127+
protected function unsubscribe(WebsocketEvent $event, Context $context): void
131128
{
132129
$eventBody = json_decode($event->getBody(), true);
133130
$channel = $eventBody['data']['channel'];
134131

135-
$this->connectionRepository->unsubscribeFromChannel($event->getConnectionId(), $channel);
132+
$this->subscriptionRepository->unsubscribeFromChannel($event->getConnectionId(), $channel);
136133

137-
return $this->jsonResponse([
134+
$this->sendMessage($event, $context, [
138135
'event' => 'unsubscription_succeeded',
139136
'channel' => $channel,
140137
'data' => [],
141138
]);
142139
}
143140

144-
protected function jsonResponse(array $data): HttpResponse
141+
public function sendMessage(WebsocketEvent $event, Context $context, array $data): void
145142
{
146-
return new HttpResponse(json_encode($data, JSON_THROW_ON_ERROR));
143+
$this->connectionRepository->sendMessage($event->getConnectionId(), json_encode($data, JSON_THROW_ON_ERROR));
147144
}
148145
}

src/ServiceProvider.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Georgeboot\LaravelEchoApiGateway;
44

5+
use Georgeboot\LaravelEchoApiGateway\Commands\VaporHandle;
56
use Illuminate\Broadcasting\Broadcasters\Broadcaster;
67
use Illuminate\Broadcasting\BroadcastManager;
78
use Illuminate\Support\Facades\Config;
@@ -36,6 +37,10 @@ public function register()
3637
config('laravel-echo-api-gateway')
3738
);
3839
});
40+
41+
$this->commands([
42+
VaporHandle::class,
43+
]);
3944
}
4045

4146
public function boot(BroadcastManager $broadcastManager): void

tests/HandlerTest.php

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
11
<?php
22

33
use Bref\Context\Context;
4+
use Georgeboot\LaravelEchoApiGateway\ConnectionRepository;
45
use Georgeboot\LaravelEchoApiGateway\Handler;
56
use Georgeboot\LaravelEchoApiGateway\SubscriptionRepository;
67
use Mockery\Mock;
78

89
it('can subscribe to open channels', function () {
9-
$mock = Mockery::mock(SubscriptionRepository::class, function ($mock) {
10+
app()->instance(SubscriptionRepository::class, Mockery::mock(SubscriptionRepository::class, function ($mock) {
1011
/** @var Mock $mock */
1112
$mock->shouldReceive('subscribeToChannel')->withArgs(function (string $connectionId, string $channel): bool {
1213
return $connectionId === 'connection-id-1' and $channel === 'test-channel';
1314
})->once();
14-
});
15+
}));
1516

16-
app()->instance(SubscriptionRepository::class, $mock);
17+
app()->instance(ConnectionRepository::class, Mockery::mock(ConnectionRepository::class, function ($mock) {
18+
/** @var Mock $mock */
19+
$mock->shouldReceive('sendMessage')->withArgs(function (string $connectionId, string $data): bool {
20+
return $connectionId === 'connection-id-1' and $data === '{"event":"subscription_succeeded","channel":"test-channel","data":[]}';
21+
})->once();
22+
}));
1723

1824
/** @var Handler $handler */
1925
$handler = app(Handler::class);
2026

2127
$context = new Context('request-id-1', 50_000, 'function-arn', 'trace-id-1');
2228

23-
$response = $handler->handle([
29+
$handler->handle([
2430
'requestContext' => [
2531
'routeKey' => 'my-test-route-key',
2632
'eventType' => 'MESSAGE',
@@ -31,26 +37,29 @@
3137
],
3238
'body' => json_encode(['event' => 'subscribe', 'data' => ['channel' => 'test-channel']]),
3339
], $context);
34-
35-
expect($response['body'])->toBeJson()->toEqual('{"event":"subscription_succeeded","channel":"test-channel","data":[]}');
3640
});
3741

3842
it('can unsubscribe from a channel', function () {
39-
$mock = Mockery::mock(SubscriptionRepository::class, function ($mock) {
43+
app()->instance(SubscriptionRepository::class, Mockery::mock(SubscriptionRepository::class, function ($mock) {
4044
/** @var Mock $mock */
4145
$mock->shouldReceive('unsubscribeFromChannel')->withArgs(function (string $connectionId, string $channel): bool {
4246
return $connectionId === 'connection-id-1' and $channel === 'test-channel';
4347
})->once();
44-
});
48+
}));
4549

46-
app()->instance(SubscriptionRepository::class, $mock);
50+
app()->instance(ConnectionRepository::class, Mockery::mock(ConnectionRepository::class, function ($mock) {
51+
/** @var Mock $mock */
52+
$mock->shouldReceive('sendMessage')->withArgs(function (string $connectionId, string $data): bool {
53+
return $connectionId === 'connection-id-1' and $data === '{"event":"unsubscription_succeeded","channel":"test-channel","data":[]}';
54+
})->once();
55+
}));
4756

4857
/** @var Handler $handler */
4958
$handler = app(Handler::class);
5059

5160
$context = new Context('request-id-1', 50_000, 'function-arn', 'trace-id-1');
5261

53-
$response = $handler->handle([
62+
$handler->handle([
5463
'requestContext' => [
5564
'routeKey' => 'my-test-route-key',
5665
'eventType' => 'MESSAGE',
@@ -61,6 +70,4 @@
6170
],
6271
'body' => json_encode(['event' => 'unsubscribe', 'data' => ['channel' => 'test-channel']]),
6372
], $context);
64-
65-
expect($response['body'])->toBeJson()->toEqual('{"event":"unsubscription_succeeded","channel":"test-channel","data":[]}');
6673
});

0 commit comments

Comments
 (0)