// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using System.Text;
using Squidex.Domain.Apps.Core.Rules.Deprecated;
using Squidex.Flows;
using Squidex.Flows.Steps.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Squidex.Extensions.Actions.Webhook;
[FlowStep(
Title = "Webhook",
IconImage = "",
IconColor = "#4bb958",
Display = "Send webhook",
Description = "Invoke HTTP endpoints on a target system.",
ReadMore = "https://en.wikipedia.org/wiki/Webhook")]
public sealed record WebhookFlowStep : FlowStep, IConvertibleToAction
{
[LocalizedRequired]
[Display(Name = "Method", Description = "The type of the request.")]
public WebhookMethod Method { get; set; }
[LocalizedRequired]
[Display(Name = "Url", Description = "The URL to the webhook.")]
[Expression]
public Uri Url { get; set; }
[Expression(ExpressionFallback.Envelope)]
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[Editor(FlowStepEditor.TextArea)]
public string? Payload { get; set; }
[Expression]
[Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")]
[Editor(FlowStepEditor.TextArea)]
public string? Headers { get; set; }
[Display(Name = "Payload Type", Description = "The mime type of the payload.")]
[Editor(FlowStepEditor.Text)]
public string? PayloadType { get; set; }
[Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the payload signature.")]
[Editor(FlowStepEditor.Text)]
public string? SharedSecret { get; set; }
public override async ValueTask ExecuteAsync(FlowExecutionContext executionContext,
CancellationToken ct)
{
var method = HttpMethod.Post;
switch (Method)
{
case WebhookMethod.PUT:
method = HttpMethod.Put;
break;
case WebhookMethod.GET:
method = HttpMethod.Get;
break;
case WebhookMethod.DELETE:
method = HttpMethod.Delete;
break;
case WebhookMethod.PATCH:
method = HttpMethod.Patch;
break;
}
string? requestBody = null;
var request = new HttpRequestMessage(method, Url);
if (!string.IsNullOrEmpty(Payload) && Method != WebhookMethod.GET)
{
var mediaType = PayloadType;
if (string.IsNullOrEmpty(mediaType))
{
mediaType = "application/json";
}
requestBody = Payload;
request.Content = new StringContent(Payload, Encoding.UTF8, mediaType);
}
var headers = ParseHeaders();
if (headers != null)
{
foreach (var (key, value) in headers)
{
request.Headers.TryAddWithoutValidation(key, value);
}
}
if (!string.IsNullOrWhiteSpace(SharedSecret))
{
var signature = $"{Payload}{SharedSecret}".ToSha256Base64();
request.Headers.Add("X-Signature", signature);
}
if (executionContext.IsSimulation)
{
executionContext.LogSkipSimulation(
HttpDumpFormatter.BuildDump(request, null, requestBody, null));
return Next();
}
var httpClient = executionContext.Resolve().CreateClient("FlowClient");
var (_, dump) = await httpClient.SendAsync(executionContext, request, requestBody, ct);
executionContext.Log("HTTP request sent", dump);
return Next();
}
private Dictionary? ParseHeaders()
{
if (string.IsNullOrWhiteSpace(Headers))
{
return null;
}
var headersDictionary = new Dictionary();
var lines = Headers.Split('\n');
foreach (var line in lines)
{
var indexEqual = line.IndexOf('=', StringComparison.Ordinal);
if (indexEqual > 0 && indexEqual < line.Length - 1)
{
var headerKey = line[..indexEqual];
var headerValue = line[(indexEqual + 1)..];
headersDictionary[headerKey] = headerValue!;
}
}
return headersDictionary;
}
public RuleAction ToAction()
{
return SimpleMapper.Map(this, new WebhookAction());
}
}