Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Broadcasting\Broadcasters;
4
 
5
use Ably\AblyRest;
6
use Ably\Exceptions\AblyException;
7
use Ably\Models\Message as AblyMessage;
8
use Illuminate\Broadcasting\BroadcastException;
9
use Illuminate\Support\Str;
10
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
11
 
12
/**
13
 * @author Matthew Hall (matthall28@gmail.com)
14
 * @author Taylor Otwell (taylor@laravel.com)
15
 */
16
class AblyBroadcaster extends Broadcaster
17
{
18
    /**
19
     * The AblyRest SDK instance.
20
     *
21
     * @var \Ably\AblyRest
22
     */
23
    protected $ably;
24
 
25
    /**
26
     * Create a new broadcaster instance.
27
     *
28
     * @param  \Ably\AblyRest  $ably
29
     * @return void
30
     */
31
    public function __construct(AblyRest $ably)
32
    {
33
        $this->ably = $ably;
34
    }
35
 
36
    /**
37
     * Authenticate the incoming request for a given channel.
38
     *
39
     * @param  \Illuminate\Http\Request  $request
40
     * @return mixed
41
     *
42
     * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
43
     */
44
    public function auth($request)
45
    {
46
        $channelName = $this->normalizeChannelName($request->channel_name);
47
 
48
        if (empty($request->channel_name) ||
49
            ($this->isGuardedChannel($request->channel_name) &&
50
            ! $this->retrieveUser($request, $channelName))) {
51
            throw new AccessDeniedHttpException;
52
        }
53
 
54
        return parent::verifyUserCanAccessChannel(
55
            $request, $channelName
56
        );
57
    }
58
 
59
    /**
60
     * Return the valid authentication response.
61
     *
62
     * @param  \Illuminate\Http\Request  $request
63
     * @param  mixed  $result
64
     * @return mixed
65
     */
66
    public function validAuthenticationResponse($request, $result)
67
    {
68
        if (str_starts_with($request->channel_name, 'private')) {
69
            $signature = $this->generateAblySignature(
70
                $request->channel_name, $request->socket_id
71
            );
72
 
73
            return ['auth' => $this->getPublicToken().':'.$signature];
74
        }
75
 
76
        $channelName = $this->normalizeChannelName($request->channel_name);
77
 
78
        $user = $this->retrieveUser($request, $channelName);
79
 
80
        $broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
81
                    ? $user->getAuthIdentifierForBroadcasting()
82
                    : $user->getAuthIdentifier();
83
 
84
        $signature = $this->generateAblySignature(
85
            $request->channel_name,
86
            $request->socket_id,
87
            $userData = array_filter([
88
                'user_id' => (string) $broadcastIdentifier,
89
                'user_info' => $result,
90
            ])
91
        );
92
 
93
        return [
94
            'auth' => $this->getPublicToken().':'.$signature,
95
            'channel_data' => json_encode($userData),
96
        ];
97
    }
98
 
99
    /**
100
     * Generate the signature needed for Ably authentication headers.
101
     *
102
     * @param  string  $channelName
103
     * @param  string  $socketId
104
     * @param  array|null  $userData
105
     * @return string
106
     */
107
    public function generateAblySignature($channelName, $socketId, $userData = null)
108
    {
109
        return hash_hmac(
110
            'sha256',
111
            sprintf('%s:%s%s', $socketId, $channelName, $userData ? ':'.json_encode($userData) : ''),
112
            $this->getPrivateToken(),
113
        );
114
    }
115
 
116
    /**
117
     * Broadcast the given event.
118
     *
119
     * @param  array  $channels
120
     * @param  string  $event
121
     * @param  array  $payload
122
     * @return void
123
     *
124
     * @throws \Illuminate\Broadcasting\BroadcastException
125
     */
126
    public function broadcast(array $channels, $event, array $payload = [])
127
    {
128
        try {
129
            foreach ($this->formatChannels($channels) as $channel) {
130
                $this->ably->channels->get($channel)->publish(
131
                    $this->buildAblyMessage($event, $payload)
132
                );
133
            }
134
        } catch (AblyException $e) {
135
            throw new BroadcastException(
136
                sprintf('Ably error: %s', $e->getMessage())
137
            );
138
        }
139
    }
140
 
141
    /**
142
     * Build an Ably message object for broadcasting.
143
     *
144
     * @param  string  $event
145
     * @param  array  $payload
146
     * @return \Ably\Models\Message
147
     */
148
    protected function buildAblyMessage($event, array $payload = [])
149
    {
150
        return tap(new AblyMessage, function ($message) use ($event, $payload) {
151
            $message->name = $event;
152
            $message->data = $payload;
153
            $message->connectionKey = data_get($payload, 'socket');
154
        });
155
    }
156
 
157
    /**
158
     * Return true if the channel is protected by authentication.
159
     *
160
     * @param  string  $channel
161
     * @return bool
162
     */
163
    public function isGuardedChannel($channel)
164
    {
165
        return Str::startsWith($channel, ['private-', 'presence-']);
166
    }
167
 
168
    /**
169
     * Remove prefix from channel name.
170
     *
171
     * @param  string  $channel
172
     * @return string
173
     */
174
    public function normalizeChannelName($channel)
175
    {
176
        if ($this->isGuardedChannel($channel)) {
177
            return str_starts_with($channel, 'private-')
178
                        ? Str::replaceFirst('private-', '', $channel)
179
                        : Str::replaceFirst('presence-', '', $channel);
180
        }
181
 
182
        return $channel;
183
    }
184
 
185
    /**
186
     * Format the channel array into an array of strings.
187
     *
188
     * @param  array  $channels
189
     * @return array
190
     */
191
    protected function formatChannels(array $channels)
192
    {
193
        return array_map(function ($channel) {
194
            $channel = (string) $channel;
195
 
196
            if (Str::startsWith($channel, ['private-', 'presence-'])) {
197
                return str_starts_with($channel, 'private-')
198
                    ? Str::replaceFirst('private-', 'private:', $channel)
199
                    : Str::replaceFirst('presence-', 'presence:', $channel);
200
            }
201
 
202
            return 'public:'.$channel;
203
        }, $channels);
204
    }
205
 
206
    /**
207
     * Get the public token value from the Ably key.
208
     *
209
     * @return mixed
210
     */
211
    protected function getPublicToken()
212
    {
213
        return Str::before($this->ably->options->key, ':');
214
    }
215
 
216
    /**
217
     * Get the private token value from the Ably key.
218
     *
219
     * @return mixed
220
     */
221
    protected function getPrivateToken()
222
    {
223
        return Str::after($this->ably->options->key, ':');
224
    }
225
 
226
    /**
227
     * Get the underlying Ably SDK instance.
228
     *
229
     * @return \Ably\AblyRest
230
     */
231
    public function getAbly()
232
    {
233
        return $this->ably;
234
    }
235
}