5
5
use Composer \InstalledVersions ;
6
6
use Illuminate \Console \Command ;
7
7
use Illuminate \Filesystem \Filesystem ;
8
+ use Illuminate \Support \Env ;
8
9
use Illuminate \Support \Facades \Process ;
9
10
use Symfony \Component \Console \Attribute \AsCommand ;
10
11
11
12
use function Illuminate \Support \artisan_binary ;
12
13
use function Illuminate \Support \php_binary ;
13
14
use function Laravel \Prompts \confirm ;
15
+ use function Laravel \Prompts \password ;
16
+ use function Laravel \Prompts \select ;
17
+ use function Laravel \Prompts \text ;
14
18
15
19
#[AsCommand(name: 'install:broadcasting ' )]
16
20
class BroadcastingInstallCommand extends Command
@@ -26,6 +30,9 @@ class BroadcastingInstallCommand extends Command
26
30
{--composer=global : Absolute path to the Composer binary which should be used to install packages}
27
31
{--force : Overwrite any existing broadcasting routes file}
28
32
{--without-reverb : Do not prompt to install Laravel Reverb}
33
+ {--reverb : Install Laravel Reverb as the default broadcaster}
34
+ {--pusher : Install Pusher as the default broadcaster}
35
+ {--ably : Install Ably as the default broadcaster}
29
36
{--without-node : Do not prompt to install Node dependencies} ' ;
30
37
31
38
/**
@@ -35,6 +42,23 @@ class BroadcastingInstallCommand extends Command
35
42
*/
36
43
protected $ description = 'Create a broadcasting channel routes file ' ;
37
44
45
+ /**
46
+ * The broadcasting driver to use.
47
+ *
48
+ * @var string|null
49
+ */
50
+ protected $ driver = null ;
51
+
52
+ /**
53
+ * The framework packages to install.
54
+ *
55
+ * @var array
56
+ */
57
+ protected $ frameworkPackages = [
58
+ 'react ' => '@laravel/echo-react ' ,
59
+ 'vue ' => '@laravel/echo-vue ' ,
60
+ ];
61
+
38
62
/**
39
63
* Execute the console command.
40
64
*
@@ -54,25 +78,44 @@ public function handle()
54
78
$ this ->uncommentChannelsRoutesFile ();
55
79
$ this ->enableBroadcastServiceProvider ();
56
80
57
- // Install bootstrapping...
58
- if (! file_exists ($ echoScriptPath = $ this ->laravel ->resourcePath ('js/echo.js ' ))) {
59
- if (! is_dir ($ directory = $ this ->laravel ->resourcePath ('js ' ))) {
60
- mkdir ($ directory , 0755 , true );
61
- }
81
+ $ this ->driver = $ this ->resolveDriver ();
62
82
63
- copy (__DIR__ .'/stubs/echo-js.stub ' , $ echoScriptPath );
64
- }
83
+ Env::writeVariable ('BROADCAST_CONNECTION ' , $ this ->driver , $ this ->laravel ->basePath ('.env ' ), true );
65
84
66
- if (file_exists ($ bootstrapScriptPath = $ this ->laravel ->resourcePath ('js/bootstrap.js ' ))) {
67
- $ bootstrapScript = file_get_contents (
68
- $ bootstrapScriptPath
69
- );
85
+ $ this ->collectDriverConfig ();
86
+ $ this ->installDriverPackages ();
87
+
88
+ if ($ this ->isUsingSupportedFramework ()) {
89
+ // If this is a supported framework, we will use the framework-specific Echo helpers...
90
+ $ this ->injectFrameworkSpecificConfiguration ();
91
+ } else {
92
+ // Standard JavaScript implementation...
93
+ if (! file_exists ($ echoScriptPath = $ this ->laravel ->resourcePath ('js/echo.js ' ))) {
94
+ if (! is_dir ($ directory = $ this ->laravel ->resourcePath ('js ' ))) {
95
+ mkdir ($ directory , 0755 , true );
96
+ }
97
+
98
+ $ stubPath = __DIR__ .'/stubs/echo-js- ' .$ this ->driver .'.stub ' ;
99
+
100
+ if (! file_exists ($ stubPath )) {
101
+ $ stubPath = __DIR__ .'/stubs/echo-js-reverb.stub ' ;
102
+ }
103
+
104
+ copy ($ stubPath , $ echoScriptPath );
105
+ }
70
106
71
- if (! str_contains ( $ bootstrapScript , ' ./echo ' )) {
72
- file_put_contents (
73
- $ bootstrapScriptPath ,
74
- trim ( $ bootstrapScript . PHP_EOL . file_get_contents ( __DIR__ . ' /stubs/echo-bootstrap-js.stub ' )). PHP_EOL ,
107
+ // Only add the bootstrap import for the standard JS implementation...
108
+ if ( file_exists ( $ bootstrapScriptPath = $ this -> laravel -> resourcePath ( ' js/bootstrap.js ' ))) {
109
+ $ bootstrapScript = file_get_contents (
110
+ $ bootstrapScriptPath
75
111
);
112
+
113
+ if (! str_contains ($ bootstrapScript , './echo ' )) {
114
+ file_put_contents (
115
+ $ bootstrapScriptPath ,
116
+ trim ($ bootstrapScript .PHP_EOL .file_get_contents (__DIR__ .'/stubs/echo-bootstrap-js.stub ' )).PHP_EOL ,
117
+ );
118
+ }
76
119
}
77
120
}
78
121
@@ -118,8 +161,10 @@ protected function enableBroadcastServiceProvider()
118
161
{
119
162
$ filesystem = new Filesystem ;
120
163
121
- if (! $ filesystem ->exists (app ()->configPath ('app.php ' )) ||
122
- ! $ filesystem ->exists ('app/Providers/BroadcastServiceProvider.php ' )) {
164
+ if (
165
+ ! $ filesystem ->exists (app ()->configPath ('app.php ' )) ||
166
+ ! $ filesystem ->exists ('app/Providers/BroadcastServiceProvider.php ' )
167
+ ) {
123
168
return ;
124
169
}
125
170
@@ -134,14 +179,179 @@ protected function enableBroadcastServiceProvider()
134
179
}
135
180
}
136
181
182
+ /**
183
+ * Collect the driver configuration.
184
+ *
185
+ * @return void
186
+ */
187
+ protected function collectDriverConfig ()
188
+ {
189
+ $ envPath = $ this ->laravel ->basePath ('.env ' );
190
+
191
+ if (! file_exists ($ envPath )) {
192
+ return ;
193
+ }
194
+
195
+ match ($ this ->driver ) {
196
+ 'pusher ' => $ this ->collectPusherConfig (),
197
+ 'ably ' => $ this ->collectAblyConfig (),
198
+ default => null ,
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Install the driver packages.
204
+ *
205
+ * @return void
206
+ */
207
+ protected function installDriverPackages ()
208
+ {
209
+ $ package = match ($ this ->driver ) {
210
+ 'pusher ' => 'pusher/pusher-php-server ' ,
211
+ 'ably ' => 'ably/ably-php ' ,
212
+ default => null ,
213
+ };
214
+
215
+ if (! $ package || InstalledVersions::isInstalled ($ package )) {
216
+ return ;
217
+ }
218
+
219
+ $ this ->requireComposerPackages ($ this ->option ('composer ' ), [$ package ]);
220
+ }
221
+
222
+ /**
223
+ * Collect the Pusher configuration.
224
+ *
225
+ * @return void
226
+ */
227
+ protected function collectPusherConfig ()
228
+ {
229
+ $ appId = text ('Pusher App ID ' , 'Enter your Pusher app ID ' );
230
+ $ key = password ('Pusher App Key ' , 'Enter your Pusher app key ' );
231
+ $ secret = password ('Pusher App Secret ' , 'Enter your Pusher app secret ' );
232
+
233
+ $ cluster = select ('Pusher App Cluster ' , [
234
+ 'mt1 ' ,
235
+ 'us2 ' ,
236
+ 'us3 ' ,
237
+ 'eu ' ,
238
+ 'ap1 ' ,
239
+ 'ap2 ' ,
240
+ 'ap3 ' ,
241
+ 'ap4 ' ,
242
+ 'sa1 ' ,
243
+ ]);
244
+
245
+ Env::writeVariables ([
246
+ 'PUSHER_APP_ID ' => $ appId ,
247
+ 'PUSHER_APP_KEY ' => $ key ,
248
+ 'PUSHER_APP_SECRET ' => $ secret ,
249
+ 'PUSHER_APP_CLUSTER ' => $ cluster ,
250
+ 'PUSHER_PORT ' => 443 ,
251
+ 'PUSHER_SCHEME ' => 'https ' ,
252
+ 'VITE_PUSHER_APP_KEY ' => '${PUSHER_APP_KEY} ' ,
253
+ 'VITE_PUSHER_APP_CLUSTER ' => '${PUSHER_APP_CLUSTER} ' ,
254
+ 'VITE_PUSHER_HOST ' => '${PUSHER_HOST} ' ,
255
+ 'VITE_PUSHER_PORT ' => '${PUSHER_PORT} ' ,
256
+ 'VITE_PUSHER_SCHEME ' => '${PUSHER_SCHEME} ' ,
257
+ ], $ this ->laravel ->basePath ('.env ' ));
258
+ }
259
+
260
+ /**
261
+ * Collect the Ably configuration.
262
+ *
263
+ * @return void
264
+ */
265
+ protected function collectAblyConfig ()
266
+ {
267
+ $ this ->components ->warn ('Make sure to enable "Pusher protocol support" in your Ably app settings. ' );
268
+
269
+ $ key = password ('Ably Key ' , 'Enter your Ably key ' );
270
+
271
+ $ publicKey = explode (': ' , $ key )[0 ] ?? $ key ;
272
+
273
+ Env::writeVariables ([
274
+ 'ABLY_KEY ' => $ key ,
275
+ 'ABLY_PUBLIC_KEY ' => $ publicKey ,
276
+ 'VITE_ABLY_PUBLIC_KEY ' => '${ABLY_PUBLIC_KEY} ' ,
277
+ ], $ this ->laravel ->basePath ('.env ' ));
278
+ }
279
+
280
+ /**
281
+ * Inject Echo configuration into the application's main file.
282
+ *
283
+ * @return void
284
+ */
285
+ protected function injectFrameworkSpecificConfiguration ()
286
+ {
287
+ if ($ this ->appUsesVue ()) {
288
+ $ importPath = $ this ->frameworkPackages ['vue ' ];
289
+
290
+ $ filePaths = [
291
+ $ this ->laravel ->resourcePath ('js/app.ts ' ),
292
+ $ this ->laravel ->resourcePath ('js/app.js ' ),
293
+ ];
294
+ } else {
295
+ $ importPath = $ this ->frameworkPackages ['react ' ];
296
+
297
+ $ filePaths = [
298
+ $ this ->laravel ->resourcePath ('js/app.tsx ' ),
299
+ $ this ->laravel ->resourcePath ('js/app.jsx ' ),
300
+ ];
301
+ }
302
+
303
+ $ filePath = array_filter ($ filePaths , function ($ path ) {
304
+ return file_exists ($ path );
305
+ })[0 ] ?? null ;
306
+
307
+ if (! $ filePath ) {
308
+ $ this ->components ->warn ("Could not find file [ {$ filePaths [0 ]}]. Skipping automatic Echo configuration. " );
309
+
310
+ return ;
311
+ }
312
+
313
+ $ contents = file_get_contents ($ filePath );
314
+
315
+ $ echoCode = <<<JS
316
+ import { configureEcho } from ' {$ importPath }';
317
+
318
+ configureEcho({
319
+ broadcaster: ' {$ this ->driver }',
320
+ });
321
+ JS ;
322
+
323
+ preg_match_all ('/^import .+;$/m ' , $ contents , $ matches );
324
+
325
+ if (empty ($ matches [0 ])) {
326
+ // Add the Echo configuration to the top of the file if no import statements are found...
327
+ $ newContents = $ echoCode .PHP_EOL .$ contents ;
328
+
329
+ file_put_contents ($ filePath , $ newContents );
330
+ } else {
331
+ // Add Echo configuration after the last import...
332
+ $ lastImport = end ($ matches [0 ]);
333
+
334
+ $ positionOfLastImport = strrpos ($ contents , $ lastImport );
335
+
336
+ if ($ positionOfLastImport !== false ) {
337
+ $ insertPosition = $ positionOfLastImport + strlen ($ lastImport );
338
+ $ newContents = substr ($ contents , 0 , $ insertPosition ).PHP_EOL .$ echoCode .substr ($ contents , $ insertPosition );
339
+
340
+ file_put_contents ($ filePath , $ newContents );
341
+ }
342
+ }
343
+
344
+ $ this ->components ->info ('Echo configuration added to [ ' .basename ($ filePath ).']. ' );
345
+ }
346
+
137
347
/**
138
348
* Install Laravel Reverb into the application if desired.
139
349
*
140
350
* @return void
141
351
*/
142
352
protected function installReverb ()
143
353
{
144
- if ($ this ->option ('without-reverb ' ) || InstalledVersions::isInstalled ('laravel/reverb ' )) {
354
+ if ($ this ->driver !== ' reverb ' || $ this -> option ('without-reverb ' ) || InstalledVersions::isInstalled ('laravel/reverb ' )) {
145
355
return ;
146
356
}
147
357
@@ -199,6 +409,12 @@ protected function installNodeDependencies()
199
409
];
200
410
}
201
411
412
+ if ($ this ->appUsesVue ()) {
413
+ $ commands [0 ] .= ' ' .$ this ->frameworkPackages ['vue ' ];
414
+ } elseif ($ this ->appUsesReact ()) {
415
+ $ commands [0 ] .= ' ' .$ this ->frameworkPackages ['react ' ];
416
+ }
417
+
202
418
$ command = Process::command (implode (' && ' , $ commands ))
203
419
->path (base_path ());
204
420
@@ -212,4 +428,79 @@ protected function installNodeDependencies()
212
428
$ this ->components ->info ('Node dependencies installed successfully. ' );
213
429
}
214
430
}
431
+
432
+ /**
433
+ * Resolve the provider to use based on the user's choice.
434
+ *
435
+ * @return string
436
+ */
437
+ protected function resolveDriver (): string
438
+ {
439
+ if ($ this ->option ('reverb ' )) {
440
+ return 'reverb ' ;
441
+ }
442
+
443
+ if ($ this ->option ('pusher ' )) {
444
+ return 'pusher ' ;
445
+ }
446
+
447
+ if ($ this ->option ('ably ' )) {
448
+ return 'ably ' ;
449
+ }
450
+
451
+ return select ('Which broadcasting driver would you like to use? ' , [
452
+ 'reverb ' => 'Laravel Reverb ' ,
453
+ 'pusher ' => 'Pusher ' ,
454
+ 'ably ' => 'Ably ' ,
455
+ ]);
456
+ }
457
+
458
+ /**
459
+ * Detect if the user is using a supported framework (React or Vue).
460
+ *
461
+ * @return bool
462
+ */
463
+ protected function isUsingSupportedFramework (): bool
464
+ {
465
+ return $ this ->appUsesReact () || $ this ->appUsesVue ();
466
+ }
467
+
468
+ /**
469
+ * Detect if the user is using React.
470
+ *
471
+ * @return bool
472
+ */
473
+ protected function appUsesReact (): bool
474
+ {
475
+ return $ this ->packageDependenciesInclude ('react ' );
476
+ }
477
+
478
+ /**
479
+ * Detect if the user is using Vue.
480
+ *
481
+ * @return bool
482
+ */
483
+ protected function appUsesVue (): bool
484
+ {
485
+ return $ this ->packageDependenciesInclude ('vue ' );
486
+ }
487
+
488
+ /**
489
+ * Detect if the package is installed.
490
+ *
491
+ * @return bool
492
+ */
493
+ protected function packageDependenciesInclude (string $ package ): bool
494
+ {
495
+ $ packageJsonPath = $ this ->laravel ->basePath ('package.json ' );
496
+
497
+ if (! file_exists ($ packageJsonPath )) {
498
+ return false ;
499
+ }
500
+
501
+ $ packageJson = json_decode (file_get_contents ($ packageJsonPath ), true );
502
+
503
+ return isset ($ packageJson ['dependencies ' ][$ package ]) ||
504
+ isset ($ packageJson ['devDependencies ' ][$ package ]);
505
+ }
215
506
}
0 commit comments