Skip to content

Commit cd2f173

Browse files
author
Marc Lundgren
committed
rpc to handle post message: focus
1 parent 5981589 commit cd2f173

6 files changed

Lines changed: 448 additions & 0 deletions

File tree

packages/component/src/Composer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import ModalDialogComposer from './providers/ModalDialog/ModalDialogComposer';
4949
import ReducedMotionComposer from './providers/ReducedMotion/ReducedMotionComposer';
5050
import useTheme from './providers/Theme/useTheme';
5151
import createDefaultSendBoxMiddleware from './SendBox/createMiddleware';
52+
import PostMessageListener from './PostMessageListener';
5253
import createDefaultSendBoxToolbarMiddleware from './SendBoxToolbar/createMiddleware';
5354
import createStyleSet from './Styles/createStyleSet';
5455
import WebChatTheme from './Styles/WebChatTheme';
@@ -477,6 +478,7 @@ const InternalComposer = ({
477478
>
478479
{children}
479480
{onTelemetry && <UITracker />}
481+
<PostMessageListener />
480482
</ComposerCore>
481483
</DecoratorComposer>
482484
</BuiltInDecorator>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useEffect } from 'react';
2+
3+
import useFocus from './hooks/useFocus';
4+
5+
const PostMessageListener = () => {
6+
const focus = useFocus();
7+
8+
useEffect(() => {
9+
const handleMessage = async (event: MessageEvent) => {
10+
if (event.source !== window.parent) {
11+
return;
12+
}
13+
14+
const { type, target } = event.data ?? {};
15+
16+
if (type === 'WEBCHAT_FOCUS') {
17+
try {
18+
await focus(target);
19+
} catch (error) {
20+
console.error('WebChat focus error:', error);
21+
}
22+
}
23+
};
24+
25+
window.addEventListener('message', handleMessage);
26+
27+
return () => {
28+
window.removeEventListener('message', handleMessage);
29+
};
30+
}, [focus]);
31+
32+
return null;
33+
};
34+
35+
export default PostMessageListener;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Sample - Cross-iframe RPC focus control
2+
3+
This sample demonstrates how to control WebChat focus from a parent application when WebChat is embedded in an iframe.
4+
5+
## Background
6+
7+
When WebChat is running inside an iframe, the parent application cannot directly access the DOM elements inside the iframe due to security restrictions. This sample shows how to use `postMessage` communication to send focus commands from the parent window to WebChat.
8+
9+
## What this sample does
10+
11+
- Demonstrates cross-iframe communication using `postMessage`
12+
- Shows how to focus different parts of WebChat from an external application
13+
- Provides a complete working example of parent-iframe RPC communication
14+
15+
## How to run this sample
16+
17+
### Option 1: Using npx serve (recommended)
18+
19+
1. Navigate to this sample's folder:
20+
```bash
21+
cd samples/04.api/o.iframe-rpc-focus/
22+
```
23+
2. Start a local server:
24+
```bash
25+
npx serve
26+
```
27+
3. Open your browser to the URL shown (typically http://localhost:3000)
28+
29+
### Option 2: Direct file opening
30+
31+
1. Navigate to this sample's folder: `samples/04.api/o.iframe-rpc-focus/`
32+
2. Open `index.html` in your web browser
33+
3. Note: Some features may not work due to CORS restrictions without a server
34+
35+
## How it works
36+
37+
### 1. PostMessage Listener
38+
39+
WebChat includes a `PostMessageListener` component that listens for messages from the parent window:
40+
41+
```typescript
42+
// Inside WebChat iframe
43+
window.addEventListener('message', event => {
44+
if (event.data.type === 'WEBCHAT_FOCUS') {
45+
await focus(event.data.target);
46+
}
47+
});
48+
```
49+
50+
### 2. Parent Application RPC
51+
52+
The parent application sends focus commands via postMessage:
53+
54+
```javascript
55+
// From parent application
56+
function focusWebChatSendBox() {
57+
const iframe = document.getElementById('webchat-iframe');
58+
iframe.contentWindow.postMessage(
59+
{
60+
type: 'WEBCHAT_FOCUS',
61+
target: 'sendBox'
62+
},
63+
'*'
64+
);
65+
}
66+
```
67+
68+
### 3. Available Focus Targets
69+
70+
- `'sendBox'` - Focus the send box input with keyboard
71+
- `'sendBoxWithoutKeyboard'` - Focus the send box without showing virtual keyboard
72+
- `'main'` - Focus the main transcript area
73+
74+
## Use cases
75+
76+
This pattern is useful when:
77+
78+
- WebChat is embedded in an iframe within a larger application
79+
- You need to programmatically focus WebChat from outside the iframe
80+
- You want to integrate WebChat focus control with external UI elements
81+
- You're building a dashboard or portal that includes WebChat
82+
83+
## Implementation details
84+
85+
### CDN Version (Current Sample)
86+
87+
This sample uses the CDN version of WebChat with DOM-based focus fallback:
88+
89+
- Direct DOM manipulation to find and focus elements
90+
- Uses common selectors to locate send box and transcript
91+
- Works with any WebChat version from CDN
92+
93+
### Development Version (Recommended for Production)
94+
95+
For the full implementation with WebChat's `useFocus()` hook:
96+
97+
- Use the PostMessageListener component in packages/component/src/PostMessageListener.tsx
98+
- Provides proper focus management, accessibility support, and screen reader compatibility
99+
- Requires building WebChat from source or using a custom build
100+
101+
## Security considerations
102+
103+
The sample includes basic security measures:
104+
105+
- Only accepts messages from the parent window
106+
- Validates message structure before processing
107+
- Handles errors gracefully
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>WebChat iframe RPC Test</title>
5+
<style>
6+
body {
7+
font-family: Arial, sans-serif;
8+
margin: 20px;
9+
}
10+
.container {
11+
display: flex;
12+
gap: 20px;
13+
}
14+
.controls {
15+
flex: 0 0 200px;
16+
}
17+
.iframe-container {
18+
flex: 1;
19+
}
20+
button {
21+
display: block;
22+
margin: 10px 0;
23+
padding: 10px;
24+
width: 100%;
25+
}
26+
iframe {
27+
width: 100%;
28+
height: 600px;
29+
border: 1px solid #ccc;
30+
}
31+
.status {
32+
margin: 10px 0;
33+
padding: 10px;
34+
background: #f0f0f0;
35+
border-radius: 4px;
36+
}
37+
</style>
38+
</head>
39+
<body>
40+
<h1>WebChat iframe RPC Test</h1>
41+
<p>This demonstrates calling WebChat focus functions from a parent window via postMessage.</p>
42+
43+
<div class="container">
44+
<div class="controls">
45+
<h3>RPC Controls</h3>
46+
<button onclick="focusWebChatSendBox()">Focus Send Box</button>
47+
<button onclick="focusWebChatSendBoxWithoutKeyboard()">Focus Send Box (No Keyboard)</button>
48+
<button onclick="focusWebChatTranscript()">Focus Transcript</button>
49+
50+
<div class="status" id="status">Ready to test RPC calls</div>
51+
</div>
52+
53+
<div class="iframe-container">
54+
<h3>WebChat iframe</h3>
55+
<iframe id="webchat-iframe" src="./webchat.html"> Your browser does not support iframes. </iframe>
56+
</div>
57+
</div>
58+
59+
<script>
60+
// RPC functions for communicating with WebChat in iframe
61+
function updateStatus(message) {
62+
document.getElementById('status').textContent = message;
63+
console.log('RPC:', message);
64+
}
65+
66+
function focusWebChatSendBox() {
67+
const webChatIframe = document.getElementById('webchat-iframe');
68+
if (webChatIframe && webChatIframe.contentWindow) {
69+
webChatIframe.contentWindow.postMessage(
70+
{
71+
type: 'WEBCHAT_FOCUS',
72+
target: 'sendBox'
73+
},
74+
'*'
75+
);
76+
updateStatus('Sent focus sendBox command');
77+
} else {
78+
updateStatus('Error: iframe not found or not loaded');
79+
}
80+
}
81+
82+
function focusWebChatSendBoxWithoutKeyboard() {
83+
const webChatIframe = document.getElementById('webchat-iframe');
84+
if (webChatIframe && webChatIframe.contentWindow) {
85+
webChatIframe.contentWindow.postMessage(
86+
{
87+
type: 'WEBCHAT_FOCUS',
88+
target: 'sendBoxWithoutKeyboard'
89+
},
90+
'*'
91+
);
92+
updateStatus('Sent focus sendBox (no keyboard) command');
93+
} else {
94+
updateStatus('Error: iframe not found or not loaded');
95+
}
96+
}
97+
98+
function focusWebChatTranscript() {
99+
const webChatIframe = document.getElementById('webchat-iframe');
100+
if (webChatIframe && webChatIframe.contentWindow) {
101+
webChatIframe.contentWindow.postMessage(
102+
{
103+
type: 'WEBCHAT_FOCUS',
104+
target: 'main'
105+
},
106+
'*'
107+
);
108+
updateStatus('Sent focus transcript command');
109+
} else {
110+
updateStatus('Error: iframe not found or not loaded');
111+
}
112+
}
113+
114+
// Listen for responses from iframe
115+
window.addEventListener('message', function (event) {
116+
if (event.data && event.data.type === 'WEBCHAT_READY') {
117+
updateStatus('WebChat iframe is ready for RPC commands');
118+
}
119+
});
120+
121+
// Initialize
122+
document.addEventListener('DOMContentLoaded', function () {
123+
updateStatus('Loading WebChat iframe...');
124+
});
125+
</script>
126+
</body>
127+
</html>

0 commit comments

Comments
 (0)