2026-05-24 12:17:19 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Actions\Automation\Node;
|
|
|
|
|
|
|
|
|
|
use App\DataTransferObjects\Automation\NodeRunResult;
|
Back fixed-set automation strings with enums and consts
Replace magic strings across the automation domain with backed PHP enums
(HttpMethod, AuthType, DelayUnit, ScheduleField) and mirrored TS consts
(http-method, auth-type, delay-unit, schedule-field, condition-operator,
publish-mode), plus the existing Condition\Handle / Operator / Publish\Mode.
Also:
- require scheduled_offset via concrete-index required_if instead of
defaulting to 60 when the publish mode is scheduled
- fail the webhook node explicitly when the resolved url is empty
- localize node failure messages (fetch_rss/http/webhook)
- cast resolver/strtoupper inputs to string so a present-null config value
degrades gracefully instead of crashing
- list automations with config('app.pagination.default'), drop the perPage param
2026-06-13 18:04:24 +00:00
|
|
|
use App\Enums\Automation\HttpMethod;
|
2026-05-24 12:17:19 +00:00
|
|
|
use App\Models\AutomationRun;
|
|
|
|
|
use App\Services\Automation\ExpressionResolver;
|
2026-06-11 18:47:29 +00:00
|
|
|
use App\Services\Brand\SafeHttpFetcher;
|
2026-05-24 12:17:19 +00:00
|
|
|
use Illuminate\Support\Facades\Http;
|
2026-06-11 18:47:29 +00:00
|
|
|
use RuntimeException;
|
2026-06-13 12:56:18 +00:00
|
|
|
use Throwable;
|
2026-05-24 12:17:19 +00:00
|
|
|
|
|
|
|
|
class RunWebhookNode
|
|
|
|
|
{
|
2026-06-11 18:47:29 +00:00
|
|
|
public function __construct(
|
|
|
|
|
private ExpressionResolver $resolver,
|
|
|
|
|
private SafeHttpFetcher $safeHttp,
|
|
|
|
|
) {}
|
2026-05-24 12:17:19 +00:00
|
|
|
|
|
|
|
|
public function __invoke(AutomationRun $run, array $config): NodeRunResult
|
|
|
|
|
{
|
2026-06-11 18:47:29 +00:00
|
|
|
$context = $run->resolverContext();
|
Back fixed-set automation strings with enums and consts
Replace magic strings across the automation domain with backed PHP enums
(HttpMethod, AuthType, DelayUnit, ScheduleField) and mirrored TS consts
(http-method, auth-type, delay-unit, schedule-field, condition-operator,
publish-mode), plus the existing Condition\Handle / Operator / Publish\Mode.
Also:
- require scheduled_offset via concrete-index required_if instead of
defaulting to 60 when the publish mode is scheduled
- fail the webhook node explicitly when the resolved url is empty
- localize node failure messages (fetch_rss/http/webhook)
- cast resolver/strtoupper inputs to string so a present-null config value
degrades gracefully instead of crashing
- list automations with config('app.pagination.default'), drop the perPage param
2026-06-13 18:04:24 +00:00
|
|
|
$url = $this->resolver->resolve((string) data_get($config, 'url', ''), $context);
|
|
|
|
|
$method = strtoupper((string) data_get($config, 'method', HttpMethod::Post->value));
|
|
|
|
|
|
|
|
|
|
if ($url === '') {
|
|
|
|
|
return NodeRunResult::failed(__('automations.errors.webhook_missing_url'), [
|
|
|
|
|
'reason' => 'missing_url',
|
|
|
|
|
]);
|
|
|
|
|
}
|
2026-06-11 18:47:29 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$this->safeHttp->guardAgainstSsrf($url);
|
|
|
|
|
} catch (RuntimeException) {
|
|
|
|
|
return NodeRunResult::failed(__('automations.errors.url_not_allowed'), [
|
|
|
|
|
'reason' => 'url_not_allowed',
|
|
|
|
|
'url' => $url,
|
|
|
|
|
]);
|
|
|
|
|
}
|
2026-05-24 12:17:19 +00:00
|
|
|
$headers = [];
|
|
|
|
|
|
|
|
|
|
foreach ($config['headers'] ?? [] as $k => $v) {
|
2026-06-11 18:47:29 +00:00
|
|
|
$headers[$k] = $this->resolver->resolve((string) $v, $context);
|
2026-05-24 12:17:19 +00:00
|
|
|
}
|
|
|
|
|
|
Add expression autocomplete, side-panel editor, and richer HTTP fetch
Automations editor:
- {{ }} expression autocomplete in CodeMirror, scoped to the braces and
graph-aware (suggests only what upstream nodes provide + variables + now);
migrate the Generate prompt to CodeMirror so it shares the same completions
- Expandable editors: an expand button slides out a side-by-side panel
(matching the sidebar card), with a minimize control; the inline field
collapses to a hint while editing in the panel
- Hover-revealed editor toolbar (expand/copy) with styled tooltips so the
buttons no longer obscure the text while reading
- Beta badge on the Automations sidebar item
- Delete a single connection with Backspace/Delete (edge selection)
- Re-key node config so switching between same-type nodes refreshes the form
HTTP fetch node — cover every JSON response shape:
- Top-level array, object map (items_path=*), array of primitives, and NDJSON
- Key-based dedup via item_key_path (seen-set, FIFO-capped) for feeds without
dates; first poll records a baseline and emits nothing (date path too)
Fan-out test visibility:
- root_run_id links every forked branch back to the run that started a test,
so the test panel aggregates all branches instead of one
Fix a few pre-existing type issues (ScheduleData import, padded minute,
optional created_at).
2026-06-12 14:31:58 +00:00
|
|
|
// Parse the template as JSON FIRST, then resolve placeholders in its
|
|
|
|
|
// string leaves — so a value containing `"`/`&`/newlines can't corrupt
|
|
|
|
|
// the JSON (the final json_encode escapes it).
|
2026-06-13 19:03:19 +00:00
|
|
|
$template = (string) data_get($config, 'payload_template', '{}');
|
Add expression autocomplete, side-panel editor, and richer HTTP fetch
Automations editor:
- {{ }} expression autocomplete in CodeMirror, scoped to the braces and
graph-aware (suggests only what upstream nodes provide + variables + now);
migrate the Generate prompt to CodeMirror so it shares the same completions
- Expandable editors: an expand button slides out a side-by-side panel
(matching the sidebar card), with a minimize control; the inline field
collapses to a hint while editing in the panel
- Hover-revealed editor toolbar (expand/copy) with styled tooltips so the
buttons no longer obscure the text while reading
- Beta badge on the Automations sidebar item
- Delete a single connection with Backspace/Delete (edge selection)
- Re-key node config so switching between same-type nodes refreshes the form
HTTP fetch node — cover every JSON response shape:
- Top-level array, object map (items_path=*), array of primitives, and NDJSON
- Key-based dedup via item_key_path (seen-set, FIFO-capped) for feeds without
dates; first poll records a baseline and emits nothing (date path too)
Fan-out test visibility:
- root_run_id links every forked branch back to the run that started a test,
so the test panel aggregates all branches instead of one
Fix a few pre-existing type issues (ScheduleData import, padded minute,
optional created_at).
2026-06-12 14:31:58 +00:00
|
|
|
$trimmedTemplate = trim($template);
|
2026-06-10 23:45:01 +00:00
|
|
|
|
Add expression autocomplete, side-panel editor, and richer HTTP fetch
Automations editor:
- {{ }} expression autocomplete in CodeMirror, scoped to the braces and
graph-aware (suggests only what upstream nodes provide + variables + now);
migrate the Generate prompt to CodeMirror so it shares the same completions
- Expandable editors: an expand button slides out a side-by-side panel
(matching the sidebar card), with a minimize control; the inline field
collapses to a hint while editing in the panel
- Hover-revealed editor toolbar (expand/copy) with styled tooltips so the
buttons no longer obscure the text while reading
- Beta badge on the Automations sidebar item
- Delete a single connection with Backspace/Delete (edge selection)
- Re-key node config so switching between same-type nodes refreshes the form
HTTP fetch node — cover every JSON response shape:
- Top-level array, object map (items_path=*), array of primitives, and NDJSON
- Key-based dedup via item_key_path (seen-set, FIFO-capped) for feeds without
dates; first poll records a baseline and emits nothing (date path too)
Fan-out test visibility:
- root_run_id links every forked branch back to the run that started a test,
so the test panel aggregates all branches instead of one
Fix a few pre-existing type issues (ScheduleData import, padded minute,
optional created_at).
2026-06-12 14:31:58 +00:00
|
|
|
if ($trimmedTemplate === '' || $trimmedTemplate === 'null') {
|
|
|
|
|
$payload = [];
|
|
|
|
|
} else {
|
|
|
|
|
$decodedTemplate = json_decode($template, true);
|
2026-06-10 23:45:01 +00:00
|
|
|
|
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
|
|
|
return NodeRunResult::failed(__('automations.errors.webhook_invalid_payload_json'), [
|
|
|
|
|
'reason' => 'invalid_payload_json',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
Add expression autocomplete, side-panel editor, and richer HTTP fetch
Automations editor:
- {{ }} expression autocomplete in CodeMirror, scoped to the braces and
graph-aware (suggests only what upstream nodes provide + variables + now);
migrate the Generate prompt to CodeMirror so it shares the same completions
- Expandable editors: an expand button slides out a side-by-side panel
(matching the sidebar card), with a minimize control; the inline field
collapses to a hint while editing in the panel
- Hover-revealed editor toolbar (expand/copy) with styled tooltips so the
buttons no longer obscure the text while reading
- Beta badge on the Automations sidebar item
- Delete a single connection with Backspace/Delete (edge selection)
- Re-key node config so switching between same-type nodes refreshes the form
HTTP fetch node — cover every JSON response shape:
- Top-level array, object map (items_path=*), array of primitives, and NDJSON
- Key-based dedup via item_key_path (seen-set, FIFO-capped) for feeds without
dates; first poll records a baseline and emits nothing (date path too)
Fan-out test visibility:
- root_run_id links every forked branch back to the run that started a test,
so the test panel aggregates all branches instead of one
Fix a few pre-existing type issues (ScheduleData import, padded minute,
optional created_at).
2026-06-12 14:31:58 +00:00
|
|
|
$payload = $this->resolver->resolveStructured($decodedTemplate ?? [], $context);
|
2026-06-10 23:45:01 +00:00
|
|
|
}
|
2026-05-24 12:17:19 +00:00
|
|
|
|
2026-06-11 18:47:29 +00:00
|
|
|
if ($run->is_dry_run) {
|
|
|
|
|
return NodeRunResult::completed(output: [
|
|
|
|
|
'webhook' => ['method' => $method, 'url' => $url, 'dry_run' => true],
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 12:56:18 +00:00
|
|
|
try {
|
|
|
|
|
$response = Http::withHeaders($headers)
|
|
|
|
|
->withUserAgent(config('trypost.user_agent'))
|
2026-07-17 17:55:56 +00:00
|
|
|
->withOptions(['allow_redirects' => false])
|
2026-06-13 12:56:18 +00:00
|
|
|
->send($method, $url, ['json' => $payload]);
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
return NodeRunResult::failed(__('automations.errors.webhook_request_failed'), [
|
|
|
|
|
'reason' => 'request_failed',
|
|
|
|
|
'message' => $e->getMessage(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
2026-05-24 12:17:19 +00:00
|
|
|
|
|
|
|
|
if ($response->serverError()) {
|
|
|
|
|
return NodeRunResult::failed(__('automations.errors.webhook_server_error'), [
|
|
|
|
|
'status' => $response->status(),
|
|
|
|
|
'body' => substr($response->body(), 0, 500),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NodeRunResult::completed(output: [
|
|
|
|
|
'webhook' => [
|
|
|
|
|
'status' => $response->status(),
|
|
|
|
|
'body' => substr($response->body(), 0, 500),
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|