diff --git a/app/Events/RequestedSendWebhookMessages.php b/app/Events/RequestedSendWebhookMessages.php new file mode 100644 index 0000000000..79dad2d7aa --- /dev/null +++ b/app/Events/RequestedSendWebhookMessages.php @@ -0,0 +1,30 @@ +. + */ + +namespace FireflyIII\Events; + +use Illuminate\Queue\SerializesModels; + + +class RequestedSendWebhookMessages extends Event +{ + use SerializesModels; +} \ No newline at end of file diff --git a/app/Generator/Webhook/WebhookMessageGenerator.php b/app/Generator/Webhook/WebhookMessageGenerator.php index 5fd19816fa..1f91fb64d7 100644 --- a/app/Generator/Webhook/WebhookMessageGenerator.php +++ b/app/Generator/Webhook/WebhookMessageGenerator.php @@ -21,6 +21,8 @@ namespace FireflyIII\Generator\Webhook; +use FireflyIII\Events\RequestedSendWebhookMessages; +use FireflyIII\Events\StoredWebhookMessage; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionGroup; @@ -93,6 +95,7 @@ class WebhookMessageGenerator foreach ($this->webhooks as $webhook) { $this->runWebhook($webhook); } + event(new RequestedSendWebhookMessages); } /** @@ -166,7 +169,7 @@ class WebhookMessageGenerator * @param Webhook $webhook * @param array $message */ - private function storeMessage(Webhook $webhook, array $message): void + private function storeMessage(Webhook $webhook, array $message): WebhookMessage { $webhookMessage = new WebhookMessage; $webhookMessage->webhook()->associate($webhook); @@ -176,6 +179,8 @@ class WebhookMessageGenerator $webhookMessage->message = $message; $webhookMessage->logs = null; $webhookMessage->save(); + + return $webhookMessage; } diff --git a/app/Handlers/Events/StoredGroupEventHandler.php b/app/Handlers/Events/StoredGroupEventHandler.php index 555fbb6f97..d55e477b14 100644 --- a/app/Handlers/Events/StoredGroupEventHandler.php +++ b/app/Handlers/Events/StoredGroupEventHandler.php @@ -27,7 +27,6 @@ use FireflyIII\Generator\Webhook\WebhookMessageGenerator; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\Webhook; use FireflyIII\Repositories\Rule\RuleRepositoryInterface; -use FireflyIII\TransactionRules\Engine\RuleEngine; use FireflyIII\TransactionRules\Engine\RuleEngineInterface; use Illuminate\Support\Collection; use Log; @@ -87,8 +86,7 @@ class StoredGroupEventHandler $engine->setUser($user); $engine->setTransactionGroups(new Collection([$group])); $engine->setTrigger(Webhook::TRIGGER_STORE_TRANSACTION); - - $messages= $engine->generateMessages(); + $engine->generateMessages(); } } diff --git a/app/Handlers/Events/WebhookEventHandler.php b/app/Handlers/Events/WebhookEventHandler.php new file mode 100644 index 0000000000..699c875346 --- /dev/null +++ b/app/Handlers/Events/WebhookEventHandler.php @@ -0,0 +1,112 @@ +. + */ + +namespace FireflyIII\Handlers\Events; + + +use Exception; +use FireflyIII\Models\WebhookMessage; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ClientException; +use JsonException; +use Log; + +/** + * Class WebhookEventHandler + */ +class WebhookEventHandler +{ + /** + * + */ + public function sendWebhookMessages(): void + { + $max = (int)config('firefly.webhooks.max_attempts'); + $max = 0 === $max ? 3 : $max; + $messages = WebhookMessage::where('sent', 0) + ->where('attempts', '<=', $max) + ->get(); + Log::debug(sprintf('Going to send %d webhook message(s)', $messages->count())); + /** @var WebhookMessage $message */ + foreach ($messages as $message) { + $this->sendMessage($message); + } + } + + /** + * @param WebhookMessage $message + */ + private function sendMessage(WebhookMessage $message): void + { + Log::debug(sprintf('Trying to send webhook message #%d', $message->id)); + try { + $json = json_encode($message->message, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + $message->attempts++; + $message->logs[] = sprintf('%s: %s', date('Y-m-d H:i:s'), sprintf('Json error: %s', $e->getMessage())); + $message->save(); + + return; + } + $user = $message->webhook->user; + try { + $token = $user->generateAccessToken(); + } catch (Exception $e) { + $message->attempts++; + $message->logs[] = sprintf('%s: %s', date('Y-m-d H:i:s'), sprintf('Could not generate token: %s', $e->getMessage())); + $message->save(); + + return; + } + $accessToken = app('preferences')->getForUser($user, 'access_token', $token); + $signature = hash_hmac('sha3-256', $json, $accessToken->data, false); + $options = [ + 'body' => $json, + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Signature' => $signature, + 'connect_timeout' => 3.14, + 'User-Agent' => sprintf('FireflyIII/%s', config('firefly.version')), + 'timeout' => 10, + ], + ]; + $client = new Client; + $logs = $message->logs ?? []; + try { + $res = $client->request('GET', $message->webhook->url, $options); + $message->sent = true; + } catch (ClientException|Exception $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + $logs[] = sprintf('%s: %s', date('Y-m-d H:i:s'), $e->getMessage()); + $message->errored = true; + $message->sent = false; + } + $message->attempts++; + $message->logs = $logs; + $message->save(); + + Log::debug(sprintf('Webhook message #%d was sent. Status code %d', $message->id, $res->getStatusCode())); + Log::debug(sprintf('Response body: %s', $res->getBody())); + } + +} \ No newline at end of file diff --git a/app/Models/WebhookMessage.php b/app/Models/WebhookMessage.php index 0c8862b90d..a1113f2bfb 100644 --- a/app/Models/WebhookMessage.php +++ b/app/Models/WebhookMessage.php @@ -61,6 +61,7 @@ class WebhookMessage extends Model 'errored' => 'boolean', 'uuid' => 'string', 'message' => 'json', + 'logs' => 'json', ]; /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index eae72ec7b1..9ac1181564 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -29,8 +29,10 @@ use FireflyIII\Events\DetectedNewIPAddress; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; use FireflyIII\Events\RequestedReportOnJournals; +use FireflyIII\Events\RequestedSendWebhookMessages; use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Events\StoredTransactionGroup; +use FireflyIII\Events\StoredWebhookMessage; use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Events\UserChangedEmail; use FireflyIII\Handlers\Events\SendEmailVerificationNotification; @@ -112,6 +114,11 @@ class EventServiceProvider extends ServiceProvider AccessTokenCreated::class => [ 'FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated', ], + + // Webhook related event: + RequestedSendWebhookMessages::class => [ + 'FireflyIII\Handlers\Events\WebhookEventHandler@sendWebhookMessages', + ], ]; /** diff --git a/config/firefly.php b/config/firefly.php index 9ba834d4da..76eac98c40 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -823,16 +823,17 @@ return [ 'recurrence_total', 'recurrence_count', ], 'webhooks' => [ - 'triggers' => [ + 'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3), + 'triggers' => [ 100 => 'TRIGGER_STORE_TRANSACTION', 110 => 'TRIGGER_UPDATE_TRANSACTION', 120 => 'TRIGGER_DESTROY_TRANSACTION', ], - 'responses' => [ + 'responses' => [ 200 => 'RESPONSE_TRANSACTIONS', 210 => 'RESPONSE_ACCOUNTS', ], - 'deliveries' => [ + 'deliveries' => [ 300 => 'DELIVERY_JSON', ], ], diff --git a/database/migrations/2020_11_12_070604_changes_for_v550.php b/database/migrations/2020_11_12_070604_changes_for_v550.php index 18f4d9ef49..4746cbb740 100644 --- a/database/migrations/2020_11_12_070604_changes_for_v550.php +++ b/database/migrations/2020_11_12_070604_changes_for_v550.php @@ -138,6 +138,7 @@ class ChangesForV550 extends Migration $table->integer('webhook_id', false, true); $table->boolean('sent')->default(false); $table->boolean('errored')->default(false); + $table->unsignedTinyInteger('attempts')->default(0); $table->string('uuid',64); $table->longText('message'); $table->longText('logs')->nullable();