Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/SampleApp/Sample/local.settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"WhatsApp:ReadOnMessage": true,
"WhatsApp:ReadOnProcess": true
}
}
53 changes: 10 additions & 43 deletions src/WhatsApp/AzureFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
IOptions<MetaOptions> metaOptions,
IOptions<WhatsAppOptions> functionOptions,
ILogger<AzureFunctions> logger,
IHostEnvironment environment)

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.

Check warning on line 29 in src/WhatsApp/AzureFunctions.cs

View workflow job for this annotation

GitHub Actions / build-ubuntu-latest

Parameter 'environment' is unread.
{
readonly WhatsAppOptions functionOptions = functionOptions.Value;

Expand All @@ -39,6 +39,10 @@

if (await WhatsApp.Message.DeserializeAsync(json) is { } message)
{
if (functionOptions.ReadOnMessage is true && message.Type == MessageType.Content)
// Ignored since this can be an old, deleted message, for example
await whatsapp.MarkReadAsync(message.Service.Id, message.Id).Ignore();

// Ensure idempotent processing
var table = tableClient.GetTableClient("WhatsAppWebhook");
await table.CreateIfNotExistsAsync();
Expand All @@ -48,18 +52,8 @@
return new OkResult();
}

if (functionOptions.ReactOnMessage != null &&
message.Type == MessageType.Content)
{
try
{
await message.React(functionOptions.ReactOnMessage).SendAsync(whatsapp);
}
catch (Exception e)
{
logger.LogWarning("Failed to react to message on received: {Id}\r\n{Payload}", message.Id, e.Message);
}
}
if (functionOptions.ReactOnMessage != null && message.Type == MessageType.Content)
await message.React(functionOptions.ReactOnMessage).SendAsync(whatsapp).Ignore();

// Otherwise, queue the new message
var queue = queueClient.GetQueueClient("whatsappwebhook");
Expand All @@ -81,6 +75,10 @@

if (await WhatsApp.Message.DeserializeAsync(json) is { } message)
{
if (functionOptions.ReadOnProcess is true && message.Type == MessageType.Content)
// Ignored since this can be an old, deleted message, for example
await whatsapp.MarkReadAsync(message.Service.Id, message.Id).Ignore();

// Ensure idempotent processing at dequeue time, since we might have been called
// multiple times for the same message by WhatsApp (Message method) while processing was still
// happening (and therefore we didn't save the entity yet).
Expand All @@ -92,37 +90,6 @@
return;
}

if (message.Type == MessageType.Content)
{
if (functionOptions.ReactOnProcess != null)
{
try
{
await message.React(functionOptions.ReactOnProcess).SendAsync(whatsapp);
}
catch (Exception e)
{
logger.LogWarning("Failed to react to message on process: {Id}\r\n{Payload}", message.Id, e.Message);
}
}

// We only mark messages read in production, since in development this makes things
// much harder to debug as WhatsApp will notify deliver of these messages too!
if (environment.IsProduction())
{
try
{
// Best-effort to mark as read. This might be an old message callback,
// or the message might have been deleted.
await whatsapp.MarkReadAsync(message.Service.Id, message.Id);
}
catch (HttpRequestException e)
{
logger.LogWarning("Failed to mark message as read: {Id}\r\n{Payload}", message.Id, e.Message);
}
}
}

// Await all responses
// No action needed, just make sure all items are processed
await handler.HandleAsync([message]).ToArrayAsync();
Expand Down
1 change: 1 addition & 0 deletions src/WhatsApp/AzureFunctionsConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class AzureFunctionsConsole(
[Function("whatsapp_console")]
public async Task<IActionResult> MessageConsole([HttpTrigger(AuthorizationLevel.Anonymous, ["post", "get"], Route = "whatsappcli")] HttpRequest req)
{

// This endpoint is only available in development environments, since it allows sending messages from the debug console.
if (environment.IsProduction())
return new UnauthorizedResult();
Expand Down
23 changes: 23 additions & 0 deletions src/WhatsApp/TaskExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Runtime.CompilerServices;

namespace Devlooped.WhatsApp;

static class TaskExtensions
{
public static ConfiguredTaskAwaitable Ignore(this Task task)
=> task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);

public static async Task Ignore<T>(this Task<T> task)
{
if (!task.IsCompleted || task.IsFaulted)
{
try
{
await task.ConfigureAwait(ConfigureAwaitOptions.None);
}
catch (Exception)
{
}
}
}
}
10 changes: 10 additions & 0 deletions src/WhatsApp/WhatsAppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
/// </summary>
public class WhatsAppOptions
{
/// <summary>
/// Mark messages as read when received in the WhatsApp webhook endpoint.
/// </summary>
public bool? ReadOnMessage { get; set; }

/// <summary>
/// Mark messages as read when processing is started.
/// </summary>
public bool? ReadOnProcess { get; set; }

/// <summary>
/// An optional emoji to react with when a message is received
/// in the WhatsApp webhook endpoint.
Expand Down