JiFus
JiFus

Reputation: 968

Using Laravel Echo (Pusher) on Laravel Vapor

I had some difficulties finding out why my events are not "triggered" in the frontend (Laravel Echo + Pusher) with my application being deployed through Laravel Vapor, even though it was working out perfectly locally.

Pushers debug console was actually showing that the events are actually being dispatched to Pusher by the Laravel application.

Since I pretty much wasted around half a day finding out what was wrong (by luck, I saw my message showing up in real-time in my local environment instead of staging after posting something on staging), I though I'll spend another 10 minutes writing a post here so some people (hopefully) don't need to waste as much time.

Upvotes: 0

Views: 1641

Answers (2)

Marius Odendaal
Marius Odendaal

Reputation: 11

Solved it by providing the .env file.

When deploying your code via GitHub actions, there is no .env file in your repo to be used when you build your assets. When you deploy via local command, then it use your .env.

Let me explain:

When you deploy via local command:

vapor deploy production

You will notice it creates a .vapor directory in your project. This will be an exact copy of your project, which is use to build and pushed your project to vapor. The .vapor folder also contains a copy of your .env file of your project.

So when it build your assets, example for VITE:

npm run build

The .env is found and used to build your assets.

This resolves the import.meta.env.VITE_PUSHER_APP_KEY and import.meta.env.VITE_PUSHER_APP_CLUSTER which where not populated.

When you deploy with GuthHub actions, you need provide the .env.

Here is an extract how to pull your env from vapor in your GitHub action deployment script:

      - name: Pull ENV
        run: vapor env:pull staging
        env:
          VAPOR_API_TOKEN: ${{ secrets.VAPOR_API_TOKEN }}

      - name: Move ENV
        run: mv .env.staging .env

The above snippet code should be before your vapor deployment code:

vapor deploy production --commit="${CI_COMMIT_ID}" --message="${CI_MESSAGE}"

Upvotes: 1

JiFus
JiFus

Reputation: 968

The issue was actually the following:

  • Vapor first builds the application locally in a .vapor directory.
  • The PUSHER key from the .env loaded during this build process is the one in your local .env (!), which means it doesn't change anything if you set the MIX_PUSHER_APP_KEY and MIX_PUSHER_APP_CLUSTER environment variables in your .env.{environment} (which you got from running vapor env:pull {environment}.

I solved this in a quick (and dirty) way:

  • Add a few more environment variables:
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=eu

# Necessary in `php artisan pusher:credentials`
PUSHER_LOCAL_APP_KEY=
PUSHER_LOCAL_APP_CLUSTER=eu

# Necessary in `php artisan pusher:credentials`
PUSHER_STAGING_APP_KEY=
PUSHER_STAGING_APP_CLUSTER=eu

# Necessary in `php artisan pusher:credentials`
PUSHER_PRODUCTION_APP_KEY=
PUSHER_PRODUCTION_APP_CLUSTER=eu

MIX_PUSHER_APP_KEY="${PUSHER_LOCAL_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_LOCAL_APP_CLUSTER}"
  • Adding a new command (before yarn run production) to the Vapor build proces: php artisan pusher:credentials {environment}. So for staging you would use php artisan pusher:credentials staging.

The command looks like this:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Support\Str;

final class SwapPusherCredentials extends Command
{
    use ConfirmableTrait;

    private array $keyPatterns = [
        'PUSHER_%sAPP_KEY',
        'PUSHER_%sAPP_CLUSTER',
    ];

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'pusher:credentials {environment=production}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Sets the pusher credentials based on the current environment';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @throws \Exception
     */
    public function handle()
    {
        // Basically we're fixing a bug (?) in Vapor. Since we're building the front end locally,
        // we need to swap the pusher keys before building. If we don't do this, the front end
        // will be built with local pusher keys. When posting messages not local it's broken
        $environment = Str::upper($this->argument('environment'));

        $this->updatePusherEnvironmentVariables($environment);
    }

    /**
     * @param string $environment
     * @throws \Exception
     */
    private function updatePusherEnvironmentVariables(string $environment)
    {
        foreach ($this->keyPatterns as $pattern) {
            // 'PUSHER_LOCAL_APP_KEY' || 'PUSHER_STAGING_APP_KEY' etc.
            $variableToSet = sprintf($pattern, $environment . '_');

            // 'PUSHER_APP_KEY'
            $targetVariableName = sprintf($pattern, '');

            if (!env($targetVariableName, false)) {
                throw new \Exception('Missing environment value for ' . $targetVariableName);
            }

            $this->setVariable($targetVariableName, $variableToSet);
        }
    }

    private function setVariable(string $targetVariableName, string $variableToSet)
    {
        file_put_contents($this->laravel->environmentFilePath(), preg_replace(
            $this->replacementPattern($targetVariableName),
            $replacement = '${' . $variableToSet . '}',
            file_get_contents($this->laravel->environmentFilePath())
        ));

        $this->info("Successfully set MIX_{$targetVariableName} to {$replacement}!");
    }

    private function replacementPattern(string $targetVariableName)
    {
        // Don't remove notsurebutfixes, when removed it doesn't match the first entry
        // So it will match all keys except PUSHER_LOCAL_* for some reason.
        $escaped = '\=notsurebutfixes|\$\{' . $this->insertEnvironmentIntoKey('LOCAL',
                $targetVariableName) . '\}|\$\{' . $this->insertEnvironmentIntoKey('STAGING',
                $targetVariableName) . '\}|\$\{' . $this->insertEnvironmentIntoKey('PRODUCTION',
                $targetVariableName) . '\}';

        return "/^MIX_$targetVariableName{$escaped}/m";
    }

    // Insert the environment after PUSHER_ in any PUSHER_* like variable passed in.
    // So basically PUSHER_APP_KEY => PUSHER_LOCAL_APP_KEY
    private function insertEnvironmentIntoKey($environment, $key)
    {
        $parts = explode('_', $key, 2);

        return $parts[0] . '_' . $environment . '_' . $parts[1];
    }
}

That's it. Now the .env file in the .vapor directory will be updated during deployment to use the PUSHER_STAGING_APP_KEY and PUSHER_STAGING_APP_CLUSTER!

Upvotes: 0

Related Questions