Skip to content

Commit 2fd81bd

Browse files
authored
Merge pull request #7 from Tim-Maes/feature/bidirectional-communication
Feature: Bidirectional communication
2 parents 5ccceb0 + c7621ac commit 2fd81bd

File tree

6 files changed

+265
-163
lines changed

6 files changed

+265
-163
lines changed

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ A security-first Blazor iframe component with automatic resizing, cross-frame me
1818

1919
- **Security-First Design** - Built-in origin validation, message filtering, and sandbox isolation
2020
- **Content Security Policy** - Comprehensive CSP integration with fluent configuration API
21-
- **Cross-Frame Messaging** - Secure postMessage communication with validation
21+
- **Bidirectional Communication** - Secure postMessage communication with validation for both directions
2222
- **Sandbox Support** - Multiple security levels from permissive to paranoid isolation
2323
- **Environment-Aware** - Different configurations for development vs production
2424
- **Automatic Resizing** - Smart height adjustment based on iframe content
@@ -57,13 +57,18 @@ dotnet add package BlazorFrame
5757
<!-- Simple iframe with automatic security -->
5858
<BlazorFrame Src="https://example.com" />
5959
60-
<!-- Production-ready configuration -->
61-
<BlazorFrame Src="https://widget.example.com"
60+
<!-- Production-ready configuration with bidirectional communication -->
61+
<BlazorFrame @ref="iframeRef"
62+
Src="https://widget.example.com"
6263
SecurityOptions="@securityOptions"
6364
OnValidatedMessage="HandleMessage"
6465
OnSecurityViolation="HandleViolation" />
6566
67+
<button @onclick="SendDataToIframe">Send Data</button>
68+
6669
@code {
70+
private BlazorFrame? iframeRef;
71+
6772
private readonly MessageSecurityOptions securityOptions = new MessageSecurityOptions()
6873
.ForProduction() // Strict security settings
6974
.WithBasicSandbox() // Enable iframe sandboxing
@@ -79,7 +84,15 @@ dotnet add package BlazorFrame
7984
{
8085
Console.WriteLine($"Security violation: {violation.ValidationError}");
8186
return Task.CompletedTask;
82-
};
87+
}
88+
89+
private async Task SendDataToIframe()
90+
{
91+
if (iframeRef != null)
92+
{
93+
await iframeRef.SendTypedMessageAsync("user-data", new { userId = 123, name = "John" });
94+
}
95+
}
8396
}
8497
```
8598

docs/configuration/communication-options.md

Lines changed: 64 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,87 @@
22

33
**Cross-frame messaging and event handling for BlazorFrame**
44

5-
This guide covers all aspects of configuring communication between your Blazor application and iframe content, including message validation, origin control, and event handling.
5+
This guide covers all aspects of configuring communication between your Blazor application and iframe content, including message validation, origin control, event handling, and **bidirectional communication**.
66

77
## Message Handling Overview
88

9-
BlazorFrame provides two main approaches to handling messages:
9+
BlazorFrame provides comprehensive communication capabilities:
1010

11-
- **Validated Messages** (`OnValidatedMessage`) - Recommended for new implementations
11+
- **Iframe -> Host** (`OnValidatedMessage`) - Receive messages from iframe with validation
12+
- **Host -> Iframe** (`SendMessageAsync`) - Send messages to iframe with security validation
1213
- **Raw Messages** (`OnMessage`) - Legacy support for simple scenarios
1314

1415
## Basic Message Configuration
1516

16-
### Essential Message Handling
17+
### Bidirectional Communication
1718

1819
```razor
19-
<BlazorFrame Src="@widgetUrl"
20-
SecurityOptions="@messageOptions"
21-
OnValidatedMessage="HandleValidatedMessage"
22-
OnSecurityViolation="HandleSecurityViolation" />
20+
<BlazorFrame @ref="iframeRef"
21+
Src="@widgetUrl"
22+
OnValidatedMessage="HandleMessage" />
23+
24+
<button class="btn btn-primary" @onclick="SendDataToIframe">
25+
Send Data to Iframe
26+
</button>
2327
2428
@code {
25-
private readonly MessageSecurityOptions messageOptions = new MessageSecurityOptions()
26-
.ForProduction()
27-
.WithBasicSandbox();
29+
private BlazorFrame? iframeRef;
2830
29-
private async Task HandleValidatedMessage(IframeMessage message)
31+
// Send structured data to iframe
32+
private async Task SendDataToIframe()
3033
{
31-
Logger.LogInformation("Received message from {Origin}: {Data}",
32-
message.Origin, message.Data);
33-
34-
// Process the validated message
35-
await ProcessMessage(message);
34+
if (iframeRef == null) return;
35+
36+
var success = await iframeRef.SendMessageAsync(new
37+
{
38+
type = "data-update",
39+
timestamp = DateTime.UtcNow,
40+
data = new
41+
{
42+
userId = currentUser.Id,
43+
preferences = currentUser.Preferences,
44+
theme = currentTheme
45+
}
46+
});
47+
48+
if (success)
49+
{
50+
Logger.LogInformation("Data sent successfully to iframe");
51+
}
3652
}
3753
38-
private async Task HandleSecurityViolation(IframeMessage violation)
54+
// Send typed messages with automatic structure
55+
private async Task SendNotification()
3956
{
40-
Logger.LogWarning("Security violation: {Error}", violation.ValidationError);
41-
42-
// Handle security issues
43-
await HandleSecurityIssue(violation);
57+
await iframeRef.SendTypedMessageAsync("notification", new
58+
{
59+
message = "Hello from host!",
60+
level = "info",
61+
timestamp = DateTimeOffset.UtcNow
62+
});
63+
}
64+
65+
private async Task HandleMessage(IframeMessage message)
66+
{
67+
if (message.MessageType == "request-user-data")
68+
{
69+
// Respond to iframe's request for user data
70+
await SendUserDataToIframe();
71+
}
72+
}
73+
74+
private async Task SendUserDataToIframe()
75+
{
76+
await iframeRef.SendTypedMessageAsync("user-data-response", new
77+
{
78+
user = new
79+
{
80+
id = currentUser.Id,
81+
name = currentUser.Name,
82+
email = currentUser.Email,
83+
permissions = currentUser.Permissions
84+
}
85+
});
4486
}
4587
}
4688
```
@@ -336,139 +378,6 @@ public class MessageProcessor
336378
}
337379
```
338380

339-
## Bidirectional Communication
340-
341-
### Sending Messages to Iframe
342-
343-
```razor
344-
<BlazorFrame @ref="iframeRef"
345-
Src="@widgetUrl"
346-
OnValidatedMessage="HandleMessage" />
347-
348-
<button class="btn btn-primary" @onclick="SendDataToIframe">
349-
Send Data to Iframe
350-
</button>
351-
352-
@code {
353-
private BlazorFrame? iframeRef;
354-
355-
private async Task SendDataToIframe()
356-
{
357-
if (iframeRef == null) return;
358-
359-
var messageData = new
360-
{
361-
type = "data-update",
362-
timestamp = DateTime.UtcNow,
363-
data = new
364-
{
365-
userId = currentUser.Id,
366-
preferences = currentUser.Preferences,
367-
theme = currentTheme
368-
}
369-
};
370-
371-
await iframeRef.SendMessageAsync(messageData);
372-
}
373-
374-
private async Task HandleMessage(IframeMessage message)
375-
{
376-
if (message.MessageType == "request-user-data")
377-
{
378-
// Respond to iframe's request for user data
379-
await SendUserDataToIframe();
380-
}
381-
}
382-
383-
private async Task SendUserDataToIframe()
384-
{
385-
var userData = new
386-
{
387-
type = "user-data-response",
388-
user = new
389-
{
390-
id = currentUser.Id,
391-
name = currentUser.Name,
392-
email = currentUser.Email,
393-
permissions = currentUser.Permissions
394-
}
395-
};
396-
397-
await iframeRef.SendMessageAsync(userData);
398-
}
399-
}
400-
```
401-
402-
### Request-Response Pattern
403-
404-
```razor
405-
@code {
406-
private readonly Dictionary<string, TaskCompletionSource<object>> pendingRequests = new();
407-
408-
private async Task<T> SendRequestToIframe<T>(string requestType, object data)
409-
{
410-
var requestId = Guid.NewGuid().ToString();
411-
var tcs = new TaskCompletionSource<object>();
412-
413-
pendingRequests[requestId] = tcs;
414-
415-
try
416-
{
417-
// Send request to iframe
418-
await iframeRef.SendMessageAsync(new
419-
{
420-
type = requestType,
421-
requestId = requestId,
422-
data = data
423-
});
424-
425-
// Wait for response with timeout
426-
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
427-
cts.Token.Register(() => tcs.TrySetCanceled());
428-
429-
var response = await tcs.Task;
430-
return JsonSerializer.Deserialize<T>(response.ToString());
431-
}
432-
finally
433-
{
434-
pendingRequests.Remove(requestId);
435-
}
436-
}
437-
438-
private async Task HandleMessage(IframeMessage message)
439-
{
440-
// Handle responses to our requests
441-
if (message.MessageType?.EndsWith("-response") == true)
442-
{
443-
var responseData = JsonSerializer.Deserialize<ResponseMessage>(message.Data);
444-
445-
if (pendingRequests.TryGetValue(responseData.RequestId, out var tcs))
446-
{
447-
tcs.SetResult(responseData.Data);
448-
}
449-
}
450-
}
451-
452-
// Usage example
453-
private async Task GetIframeData()
454-
{
455-
try
456-
{
457-
var iframeData = await SendRequestToIframe<IframeDataResponse>(
458-
"get-data",
459-
new { category = "user-preferences" }
460-
);
461-
462-
Logger.LogInformation("Received iframe data: {Data}", iframeData);
463-
}
464-
catch (OperationCanceledException)
465-
{
466-
Logger.LogWarning("Request to iframe timed out");
467-
}
468-
}
469-
}
470-
```
471-
472381
## Event Handling
473382

474383
### Comprehensive Event Configuration

docs/core-features/parameters.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,39 @@ Complete reference for all BlazorFrame component parameters, their types, defaul
339339
}
340340
```
341341

342+
### OnMessageSent
343+
**Type:** `EventCallback<string>`
344+
**Description:** Fired when a message is successfully sent to the iframe.
345+
346+
```razor
347+
<BlazorFrame Src="https://example.com" OnMessageSent="HandleMessageSent" />
348+
349+
@code {
350+
private Task HandleMessageSent(string messageJson)
351+
{
352+
Logger.LogDebug("Message sent successfully: {Message}", messageJson);
353+
return Task.CompletedTask;
354+
}
355+
}
356+
```
357+
358+
### OnMessageSendFailed
359+
**Type:** `EventCallback<Exception>`
360+
**Description:** Fired when sending a message to the iframe fails.
361+
362+
```razor
363+
<BlazorFrame Src="https://example.com" OnMessageSendFailed="HandleSendFailure" />
364+
365+
@code {
366+
private Task HandleSendFailure(Exception ex)
367+
{
368+
Logger.LogError(ex, "Failed to send message to iframe");
369+
// Handle the failure appropriately
370+
ShowErrorToUser("Communication with widget failed");
371+
return Task.CompletedTask;
372+
}
373+
}
374+
```
342375
## Styling Parameters
343376

344377
### AdditionalAttributes

src/BlazorFrame/BlazorFrame.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
<ImplicitUsings>enable</ImplicitUsings>
77
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
88
<PackageId>BlazorFrame</PackageId>
9-
<Version>2.1.2</Version>
10-
<Description>A enhanced secure Blazor iFrame component with built-in origin validation and message security.</Description>
9+
<Version>2.2.0</Version>
10+
<Description>A enhanced secure Blazor iFrame component with built-in origin validation, bidirectional messaging, and comprehensive security features.</Description>
1111
<PackageProjectUrl>https://www.github.com/Tim-Maes/BlazorFrame</PackageProjectUrl>
1212
<PackageReadmeFile>README.md</PackageReadmeFile>
1313
<RepositoryUrl>https://www.github.com/Tim-Maes/BlazorFrame</RepositoryUrl>
14-
<PackageTags>blazor; iframe; wasm; security; postmessage; origin-validation;</PackageTags>
14+
<PackageTags>blazor; iframe; wasm; security; postmessage; origin-validation; bidirectional;</PackageTags>
1515
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
1616
<ApplicationIcon>BlazorFrameIcon.ico</ApplicationIcon>
1717
</PropertyGroup>

0 commit comments

Comments
 (0)