1<?php
2
3namespace Illuminate\Broadcasting;
4
5use Ably\AblyRest;
6use Closure;
7use Illuminate\Broadcasting\Broadcasters\AblyBroadcaster;
8use Illuminate\Broadcasting\Broadcasters\LogBroadcaster;
9use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
10use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
11use Illuminate\Broadcasting\Broadcasters\RedisBroadcaster;
12use Illuminate\Contracts\Broadcasting\Factory as FactoryContract;
13use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
14use Illuminate\Contracts\Bus\Dispatcher as BusDispatcherContract;
15use Illuminate\Contracts\Foundation\CachesRoutes;
16use InvalidArgumentException;
17use Psr\Log\LoggerInterface;
18use Pusher\Pusher;
19
20/**
21 * @mixin \Illuminate\Contracts\Broadcasting\Broadcaster
22 */
23class BroadcastManager implements FactoryContract
24{
25    /**
26     * The application instance.
27     *
28     * @var \Illuminate\Contracts\Container\Container
29     */
30    protected $app;
31
32    /**
33     * The array of resolved broadcast drivers.
34     *
35     * @var array
36     */
37    protected $drivers = [];
38
39    /**
40     * The registered custom driver creators.
41     *
42     * @var array
43     */
44    protected $customCreators = [];
45
46    /**
47     * Create a new manager instance.
48     *
49     * @param  \Illuminate\Contracts\Container\Container  $app
50     * @return void
51     */
52    public function __construct($app)
53    {
54        $this->app = $app;
55    }
56
57    /**
58     * Register the routes for handling broadcast authentication and sockets.
59     *
60     * @param  array|null  $attributes
61     * @return void
62     */
63    public function routes(array $attributes = null)
64    {
65        if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
66            return;
67        }
68
69        $attributes = $attributes ?: ['middleware' => ['web']];
70
71        $this->app['router']->group($attributes, function ($router) {
72            $router->match(
73                ['get', 'post'], '/broadcasting/auth',
74                '\\'.BroadcastController::class.'@authenticate'
75            )->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
76        });
77    }
78
79    /**
80     * Get the socket ID for the given request.
81     *
82     * @param  \Illuminate\Http\Request|null  $request
83     * @return string|null
84     */
85    public function socket($request = null)
86    {
87        if (! $request && ! $this->app->bound('request')) {
88            return;
89        }
90
91        $request = $request ?: $this->app['request'];
92
93        return $request->header('X-Socket-ID');
94    }
95
96    /**
97     * Begin broadcasting an event.
98     *
99     * @param  mixed|null  $event
100     * @return \Illuminate\Broadcasting\PendingBroadcast
101     */
102    public function event($event = null)
103    {
104        return new PendingBroadcast($this->app->make('events'), $event);
105    }
106
107    /**
108     * Queue the given event for broadcast.
109     *
110     * @param  mixed  $event
111     * @return void
112     */
113    public function queue($event)
114    {
115        if ($event instanceof ShouldBroadcastNow) {
116            return $this->app->make(BusDispatcherContract::class)->dispatchNow(new BroadcastEvent(clone $event));
117        }
118
119        $queue = null;
120
121        if (method_exists($event, 'broadcastQueue')) {
122            $queue = $event->broadcastQueue();
123        } elseif (isset($event->broadcastQueue)) {
124            $queue = $event->broadcastQueue;
125        } elseif (isset($event->queue)) {
126            $queue = $event->queue;
127        }
128
129        $this->app->make('queue')->connection($event->connection ?? null)->pushOn(
130            $queue, new BroadcastEvent(clone $event)
131        );
132    }
133
134    /**
135     * Get a driver instance.
136     *
137     * @param  string|null  $driver
138     * @return mixed
139     */
140    public function connection($driver = null)
141    {
142        return $this->driver($driver);
143    }
144
145    /**
146     * Get a driver instance.
147     *
148     * @param  string|null  $name
149     * @return mixed
150     */
151    public function driver($name = null)
152    {
153        $name = $name ?: $this->getDefaultDriver();
154
155        return $this->drivers[$name] = $this->get($name);
156    }
157
158    /**
159     * Attempt to get the connection from the local cache.
160     *
161     * @param  string  $name
162     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
163     */
164    protected function get($name)
165    {
166        return $this->drivers[$name] ?? $this->resolve($name);
167    }
168
169    /**
170     * Resolve the given broadcaster.
171     *
172     * @param  string  $name
173     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
174     *
175     * @throws \InvalidArgumentException
176     */
177    protected function resolve($name)
178    {
179        $config = $this->getConfig($name);
180
181        if (isset($this->customCreators[$config['driver']])) {
182            return $this->callCustomCreator($config);
183        }
184
185        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
186
187        if (! method_exists($this, $driverMethod)) {
188            throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
189        }
190
191        return $this->{$driverMethod}($config);
192    }
193
194    /**
195     * Call a custom driver creator.
196     *
197     * @param  array  $config
198     * @return mixed
199     */
200    protected function callCustomCreator(array $config)
201    {
202        return $this->customCreators[$config['driver']]($this->app, $config);
203    }
204
205    /**
206     * Create an instance of the driver.
207     *
208     * @param  array  $config
209     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
210     */
211    protected function createPusherDriver(array $config)
212    {
213        $pusher = new Pusher(
214            $config['key'], $config['secret'],
215            $config['app_id'], $config['options'] ?? []
216        );
217
218        if ($config['log'] ?? false) {
219            $pusher->setLogger($this->app->make(LoggerInterface::class));
220        }
221
222        return new PusherBroadcaster($pusher);
223    }
224
225    /**
226     * Create an instance of the driver.
227     *
228     * @param  array  $config
229     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
230     */
231    protected function createAblyDriver(array $config)
232    {
233        return new AblyBroadcaster(new AblyRest($config));
234    }
235
236    /**
237     * Create an instance of the driver.
238     *
239     * @param  array  $config
240     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
241     */
242    protected function createRedisDriver(array $config)
243    {
244        return new RedisBroadcaster(
245            $this->app->make('redis'), $config['connection'] ?? null,
246            $this->app['config']->get('database.redis.options.prefix', '')
247        );
248    }
249
250    /**
251     * Create an instance of the driver.
252     *
253     * @param  array  $config
254     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
255     */
256    protected function createLogDriver(array $config)
257    {
258        return new LogBroadcaster(
259            $this->app->make(LoggerInterface::class)
260        );
261    }
262
263    /**
264     * Create an instance of the driver.
265     *
266     * @param  array  $config
267     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
268     */
269    protected function createNullDriver(array $config)
270    {
271        return new NullBroadcaster;
272    }
273
274    /**
275     * Get the connection configuration.
276     *
277     * @param  string  $name
278     * @return array
279     */
280    protected function getConfig($name)
281    {
282        if (! is_null($name) && $name !== 'null') {
283            return $this->app['config']["broadcasting.connections.{$name}"];
284        }
285
286        return ['driver' => 'null'];
287    }
288
289    /**
290     * Get the default driver name.
291     *
292     * @return string
293     */
294    public function getDefaultDriver()
295    {
296        return $this->app['config']['broadcasting.default'];
297    }
298
299    /**
300     * Set the default driver name.
301     *
302     * @param  string  $name
303     * @return void
304     */
305    public function setDefaultDriver($name)
306    {
307        $this->app['config']['broadcasting.default'] = $name;
308    }
309
310    /**
311     * Disconnect the given disk and remove from local cache.
312     *
313     * @param  string|null  $name
314     * @return void
315     */
316    public function purge($name = null)
317    {
318        $name = $name ?? $this->getDefaultDriver();
319
320        unset($this->drivers[$name]);
321    }
322
323    /**
324     * Register a custom driver creator Closure.
325     *
326     * @param  string  $driver
327     * @param  \Closure  $callback
328     * @return $this
329     */
330    public function extend($driver, Closure $callback)
331    {
332        $this->customCreators[$driver] = $callback;
333
334        return $this;
335    }
336
337    /**
338     * Get the application instance used by the manager.
339     *
340     * @return \Illuminate\Contracts\Foundation\Application
341     */
342    public function getApplication()
343    {
344        return $this->app;
345    }
346
347    /**
348     * Set the application instance used by the manager.
349     *
350     * @param  \Illuminate\Contracts\Foundation\Application  $app
351     * @return $this
352     */
353    public function setApplication($app)
354    {
355        $this->app = $app;
356
357        return $this;
358    }
359
360    /**
361     * Forget all of the resolved driver instances.
362     *
363     * @return $this
364     */
365    public function forgetDrivers()
366    {
367        $this->drivers = [];
368
369        return $this;
370    }
371
372    /**
373     * Dynamically call the default driver instance.
374     *
375     * @param  string  $method
376     * @param  array  $parameters
377     * @return mixed
378     */
379    public function __call($method, $parameters)
380    {
381        return $this->driver()->$method(...$parameters);
382    }
383}
384