393 lines
11 KiB
Text
393 lines
11 KiB
Text
---
|
|
title: Raw Messages
|
|
description: Implement custom frontend-to-backend communication for performance-critical applications
|
|
sidebar:
|
|
order: 10
|
|
---
|
|
|
|
Raw messages provide a low-level communication channel between your frontend and backend, bypassing the standard binding system. This trades convenience for speed.
|
|
|
|
## When to Use Raw Messages
|
|
|
|
Raw messages are best suited for extreme edge cases:
|
|
|
|
- **Ultra-high-frequency updates** - Thousands of messages per second where every microsecond matters
|
|
- **Custom message protocols** - When you need complete control over the wire format
|
|
|
|
:::tip
|
|
For almost all use cases, standard [service bindings](/features/bindings/services) are recommended as they provide type safety, automatic serialization, and a better developer experience with negligible overhead.
|
|
:::
|
|
|
|
## Backend Setup
|
|
|
|
Configure the `RawMessageHandler` in your application options:
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
)
|
|
|
|
func main() {
|
|
app := application.New(application.Options{
|
|
Name: "Raw Message Demo",
|
|
Assets: application.AssetOptions{
|
|
Handler: application.BundledAssetFileServer(assets),
|
|
},
|
|
RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) {
|
|
fmt.Printf("Raw message from window '%s': %s (origin: %+v)\n", window.Name(), message, originInfo.Origin)
|
|
|
|
// Process the message and respond via events
|
|
response := processMessage(message)
|
|
window.EmitEvent("raw-response", response)
|
|
},
|
|
})
|
|
|
|
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|
Title: "My App",
|
|
Name: "main",
|
|
})
|
|
|
|
app.Run()
|
|
}
|
|
|
|
func processMessage(message string) map[string]any {
|
|
// Your custom message processing logic
|
|
return map[string]any{
|
|
"received": message,
|
|
"status": "processed",
|
|
}
|
|
}
|
|
```
|
|
|
|
### Handler Signature
|
|
|
|
```go
|
|
RawMessageHandler func(window Window, message string, originInfo *application.OriginInfo)
|
|
```
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `window` | `Window` | The window that sent the message |
|
|
| `message` | `string` | The raw message content |
|
|
| `originInfo` | `*application.OriginInfo` | Origin information about the message source |
|
|
|
|
#### OriginInfo Structure
|
|
|
|
```go
|
|
type OriginInfo struct {
|
|
Origin string
|
|
TopOrigin string
|
|
IsMainFrame bool
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `Origin` | `string` | The origin URL of the document that sent the message |
|
|
| `TopOrigin` | `string` | The top-level origin URL (may differ from Origin in iframes) |
|
|
| `IsMainFrame` | `bool` | Whether the message originated from the main frame |
|
|
|
|
#### Platform-Specific Availability
|
|
|
|
- **macOS**: `Origin` and `IsMainFrame` are provided
|
|
- **Windows**: `Origin` and `TopOrigin` are provided
|
|
- **Linux**: Only `Origin` is provided
|
|
|
|
### Origin Validation
|
|
|
|
:::caution
|
|
Never assume a message is safe because it arrives in your handler. The origin information must be validated before processing sensitive operations or operations that modify state.
|
|
:::
|
|
|
|
**Always verify the origin of incoming messages before processing them.** The `originInfo` parameter provides critical security information that must be validated to prevent unauthorized access.
|
|
Malicious content, compromised content, or unintended scripts could send raw messages. Without origin validation, you may process commands from untrusted sources. Use `originInfo` to ensure messages come from expected sources.
|
|
|
|
### Key Validation Points
|
|
|
|
- **Always check `Origin`** - Verify the origin matches your expected trusted sources (typically `wails://wails` or `http://wails.localhost` for local assets or your app's specific origin)
|
|
- **Validate `IsMainFrame`** (macOS) - Be aware if the message comes from an iframe, as this may indicate embedded content with different security contexts
|
|
- **Use `TopOrigin`** (Windows) - Verify the top-level origin when dealing with framed content
|
|
- **Reject unexpected origins** - Fail securely by rejecting messages from origins you don't explicitly allow
|
|
|
|
|
|
:::note
|
|
Messages prefixed with `wails:` are reserved for internal Wails communication and will not be passed to your handler.
|
|
:::
|
|
|
|
## Frontend Setup
|
|
|
|
Send raw messages using `System.invoke()`:
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<script type="module">
|
|
import { System, Events } from '@wailsio/runtime'
|
|
|
|
// Send raw message
|
|
document.getElementById('send').addEventListener('click', () => {
|
|
const message = document.getElementById('input').value
|
|
System.invoke(message)
|
|
})
|
|
|
|
// Listen for response
|
|
Events.On('raw-response', (event) => {
|
|
console.log('Response:', event.data)
|
|
})
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<input type="text" id="input" placeholder="Enter message" />
|
|
<button id="send">Send</button>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Using the Pre-built Bundle
|
|
|
|
If you're not using npm, access `invoke` via the global `wails` object:
|
|
|
|
```html
|
|
<script type="module" src="/wails/runtime.js"></script>
|
|
<script>
|
|
window.onload = function() {
|
|
document.getElementById('send').onclick = function() {
|
|
wails.System.invoke('my-message')
|
|
}
|
|
}
|
|
</script>
|
|
```
|
|
|
|
## Structured Messages
|
|
|
|
For complex data, serialize to JSON:
|
|
|
|
### Frontend
|
|
|
|
```javascript
|
|
import { System } from '@wailsio/runtime'
|
|
|
|
const command = {
|
|
action: 'update',
|
|
payload: {
|
|
id: 123,
|
|
value: 'new value'
|
|
}
|
|
}
|
|
|
|
System.invoke(JSON.stringify(command))
|
|
```
|
|
|
|
### Backend
|
|
|
|
```go
|
|
RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) {
|
|
var cmd struct {
|
|
Action string `json:"action"`
|
|
Payload struct {
|
|
ID int `json:"id"`
|
|
Value string `json:"value"`
|
|
} `json:"payload"`
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(message), &cmd); err != nil {
|
|
window.EmitEvent("error", err.Error())
|
|
return
|
|
}
|
|
|
|
switch cmd.Action {
|
|
case "update":
|
|
// Handle update
|
|
result := handleUpdate(cmd.Payload.ID, cmd.Payload.Value)
|
|
window.EmitEvent("update-complete", result)
|
|
default:
|
|
window.EmitEvent("error", "unknown action")
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance Comparison
|
|
|
|
| Approach | Overhead | Type Safety | Use Case |
|
|
|----------|----------|-------------|----------|
|
|
| Service Bindings | Higher | Full | General purpose |
|
|
| Raw Messages | Minimal | Manual | High-frequency, performance-critical |
|
|
|
|
### Benchmark Example
|
|
|
|
Raw messages can process significantly more messages per second compared to service bindings for simple payloads:
|
|
|
|
```go
|
|
// Raw message handler - minimal overhead
|
|
RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) {
|
|
// Direct string processing, no reflection or marshaling
|
|
counter++
|
|
}
|
|
```
|
|
|
|
## Complete Example
|
|
|
|
Here's a full example implementing a simple command protocol:
|
|
|
|
### main.go
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
)
|
|
|
|
//go:embed assets
|
|
var assets embed.FS
|
|
|
|
type Command struct {
|
|
Type string `json:"type"`
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
|
|
func main() {
|
|
app := application.New(application.Options{
|
|
Name: "Raw Message Demo",
|
|
Assets: application.AssetOptions{
|
|
Handler: application.BundledAssetFileServer(assets),
|
|
},
|
|
Mac: application.MacOptions{
|
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
|
},
|
|
RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) {
|
|
var cmd Command
|
|
if err := json.Unmarshal([]byte(message), &cmd); err != nil {
|
|
window.EmitEvent("error", map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
switch cmd.Type {
|
|
case "ping":
|
|
window.EmitEvent("pong", map[string]any{
|
|
"time": time.Now().UnixMilli(),
|
|
"window": window.Name(),
|
|
})
|
|
case "echo":
|
|
var text string
|
|
json.Unmarshal(cmd.Data, &text)
|
|
window.EmitEvent("echo", text)
|
|
default:
|
|
window.EmitEvent("error", map[string]string{
|
|
"error": fmt.Sprintf("unknown command: %s", cmd.Type),
|
|
})
|
|
}
|
|
},
|
|
})
|
|
|
|
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|
Title: "Raw Message Demo",
|
|
Name: "main",
|
|
Width: 400,
|
|
Height: 300,
|
|
})
|
|
|
|
app.Run()
|
|
}
|
|
```
|
|
|
|
### assets/index.html
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Raw Message Demo</title>
|
|
<style>
|
|
body { font-family: sans-serif; padding: 20px; }
|
|
button { margin: 5px; padding: 10px 20px; }
|
|
#output { margin-top: 20px; padding: 10px; background: #f0f0f0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Raw Message Demo</h1>
|
|
|
|
<button id="ping">Ping</button>
|
|
<button id="echo">Echo "Hello"</button>
|
|
|
|
<div id="output">Waiting for response...</div>
|
|
|
|
<script type="module">
|
|
import { System, Events } from '@wailsio/runtime'
|
|
|
|
const output = document.getElementById('output')
|
|
|
|
function send(type, data) {
|
|
System.invoke(JSON.stringify({ type, data }))
|
|
}
|
|
|
|
document.getElementById('ping').onclick = () => send('ping')
|
|
document.getElementById('echo').onclick = () => send('echo', 'Hello')
|
|
|
|
Events.On('pong', (e) => {
|
|
output.textContent = `Pong from ${e.data.window} at ${e.data.time}`
|
|
})
|
|
|
|
Events.On('echo', (e) => {
|
|
output.textContent = `Echo: ${e.data}`
|
|
})
|
|
|
|
Events.On('error', (e) => {
|
|
output.textContent = `Error: ${e.data.error}`
|
|
})
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### Do
|
|
|
|
- Use raw messages for genuinely performance-critical paths
|
|
- Implement proper error handling in your handler
|
|
- Use events to send responses back to the frontend
|
|
- Consider JSON for structured data
|
|
- Keep message processing fast to avoid blocking
|
|
|
|
### Don't
|
|
|
|
- Use raw messages when service bindings would suffice
|
|
- Forget to validate incoming messages
|
|
- Block in the handler with long-running operations (use goroutines)
|
|
- Ignore the window parameter when responses need to target specific windows
|
|
|
|
## Multi-Window Considerations
|
|
|
|
The `window` parameter identifies which window sent the message, allowing you to:
|
|
|
|
- Send responses to the correct window
|
|
- Implement window-specific behavior
|
|
- Track message sources for debugging
|
|
|
|
```go
|
|
RawMessageHandler: func(window application.Window, message string, originInfo *application.OriginInfo) {
|
|
// Respond only to the sending window
|
|
window.EmitEvent("response", result)
|
|
|
|
// Or broadcast to all windows
|
|
app.Event.Emit("broadcast", result)
|
|
}
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
- [Service Bindings](/features/bindings/services) - Standard approach for most applications
|
|
- [Events](/guides/events-reference) - Event system for backend-to-frontend communication
|
|
- [Performance](/guides/performance) - General performance optimization
|