Skip to content

Commit 57901e9

Browse files
authored
Add custom child iframe in teams test app for nested app auth e2e tests (#2649)
* Add custom child iframe in teams test app for E2E tests * Update as per reviewer's feedback
1 parent a51cd2b commit 57901e9

File tree

2 files changed

+264
-17
lines changed

2 files changed

+264
-17
lines changed

apps/teams-test-app/src/components/NestedAppAuthAPIs.tsx

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -152,36 +152,58 @@ const NestedAppAuthAPIs = (): ReactElement => {
152152
}),
153153
});
154154

155-
const AddChildIframeSection = (): React.ReactElement | null => {
155+
const AddChildIframeSection = (): React.ReactElement => {
156156
const [iframeAdded, setIframeAdded] = useState(false);
157157

158158
const addChildIframe = (): void => {
159159
if (iframeAdded) {
160-
console.log('Iframe already added.');
161160
return;
162161
}
163162

164163
const iframeContainer = document.getElementById('nestedChildIframeContainer');
165164
if (!iframeContainer) {
166-
console.error('Container not found: nestedChildIframeContainer');
165+
console.error('Iframe container not found');
167166
return;
168167
}
169168

170169
const childIframe = document.createElement('iframe');
171-
childIframe.src = `${window.location.href}?appInitializationTest=true&groupedMode=NestedAppAuthAPIs`;
170+
childIframe.src = '/naa_childIframe.html';
172171
childIframe.id = 'nestedAuthChildIframe';
173-
childIframe.width = '100%';
174-
childIframe.height = '400px';
172+
childIframe.style.width = '100%';
173+
childIframe.style.height = '400px';
175174
childIframe.style.border = 'none';
176175

177176
iframeContainer.appendChild(childIframe);
178177
setIframeAdded(true);
178+
179+
// Send payload to the iframe after it loads
180+
childIframe.onload = () => {
181+
const payloads = {
182+
defaultPayloadForBridge: NestedAppAuthRequest,
183+
defaultPayloadForTopWindow: JSON.stringify({
184+
id: '2',
185+
func: 'nestedAppAuth.execute',
186+
args: [],
187+
data: NestedAppAuthRequest,
188+
}),
189+
};
190+
childIframe.contentWindow?.postMessage(payloads, window.location.origin);
191+
};
179192
};
180193

181194
return (
182-
<div style={{ border: '5px solid black', padding: '2px', margin: '2px' }}>
183-
<h2>Add Nested Child Iframe</h2>
195+
<div
196+
className="boxAndButton"
197+
id="box_naaNestedChildIframe"
198+
style={{
199+
border: '5px solid black',
200+
padding: '5px',
201+
margin: '1px',
202+
}}
203+
>
204+
<h2>Child Iframe</h2>
184205
<input
206+
id="button_addNestedChildIframe"
185207
name="button_addNestedChildIframe"
186208
type="button"
187209
value="Add Child Iframe"
@@ -191,19 +213,14 @@ const NestedAppAuthAPIs = (): ReactElement => {
191213
<div
192214
id="nestedChildIframeContainer"
193215
style={{
194-
marginTop: '2px',
195-
height: '400px',
196-
border: '2px solid red',
197-
overflow: 'hidden',
198-
display: 'flex',
199-
justifyContent: 'center',
200-
alignItems: 'center',
216+
marginTop: '20px',
217+
height: '450px',
218+
border: '1px solid red',
201219
}}
202-
></div>
220+
/>
203221
</div>
204222
);
205223
};
206-
207224
return (
208225
<ModuleWrapper title="NestedAppAuth">
209226
<CheckIsNAAChannelRecommended />
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Nested Child Iframe Content</title>
7+
<style>
8+
.input-container input[type='text'] {
9+
margin-top: 10px;
10+
padding: 5px;
11+
width: 180px;
12+
}
13+
14+
.input-container input[type='button'] {
15+
width: 100px;
16+
}
17+
18+
.input-row {
19+
display: flex;
20+
gap: 5px;
21+
margin-bottom: 10px;
22+
}
23+
24+
.response-box {
25+
display: flex;
26+
margin-top: 10px;
27+
border: 1px solid #ccc;
28+
padding: 2px;
29+
height: 30px;
30+
}
31+
32+
.default-button {
33+
margin-left: 0;
34+
}
35+
36+
.section-container h4 {
37+
margin-top: 5px;
38+
font-size: 16px;
39+
}
40+
41+
.section-container {
42+
border: 1px solid #ccc;
43+
padding: 15px;
44+
margin-bottom: 20px;
45+
border-radius: 5px;
46+
}
47+
</style>
48+
</head>
49+
<body>
50+
<div>
51+
<div class="section-container">
52+
<h4>NestedAppAuthBridge</h4>
53+
<div class="input-row">
54+
<input
55+
id="input_childPayloadForBridge"
56+
name="input_childPayloadForBridge"
57+
type="text"
58+
placeholder="Payload for NestedAppAuthBridge"
59+
/>
60+
<input
61+
id="button_sendPayloadToBridge"
62+
name="button_sendPayloadToBridge"
63+
type="button"
64+
value="Send to NAA Bridge"
65+
onClick="sendMessageToBridge()"
66+
/>
67+
</div>
68+
<input
69+
id="button_defaultPayloadForBridge"
70+
name="button_defaultPayloadForBridge"
71+
type="button"
72+
value="Default"
73+
class="default-button"
74+
onClick="setDefaultPayloadForBridge()"
75+
/>
76+
<div id="text_bridge_response_for_child" class="response-box">No response yet</div>
77+
</div>
78+
79+
<div class="section-container">
80+
<h4>Top Window</h4>
81+
<div class="input-row">
82+
<input
83+
id="input_childPayloadForTopWindow"
84+
name="input_childPayloadForTopWindow"
85+
type="text"
86+
placeholder="Payload for Top Window"
87+
/>
88+
<input
89+
id="button_sendPayloadToTopWindow"
90+
name="button_sendPayloadToTopWindow"
91+
type="button"
92+
value="Send to Top Window"
93+
onClick="sendMessageToTopWindow()"
94+
/>
95+
</div>
96+
<input
97+
id="button_defaultPayloadForTopWindow"
98+
name="button_defaultPayloadForTopWindow"
99+
type="button"
100+
value="Default"
101+
class="default-button"
102+
onClick="setDefaultPayloadForTopWindow()"
103+
/>
104+
<div id="text_topWindow_response_for_child" class="response-box">No response yet</div>
105+
</div>
106+
</div>
107+
108+
<script>
109+
let defaultPayloadForBridge = '';
110+
let defaultPayloadForTopWindow = '';
111+
112+
// Listen for messages from the parent
113+
window.addEventListener('message', (event) => {
114+
// Validate the origin if known
115+
if (event.origin !== window.location.origin) return;
116+
117+
const data = event.data;
118+
if (data.defaultPayloadForBridge) {
119+
defaultPayloadForBridge = data.defaultPayloadForBridge;
120+
}
121+
if (data.defaultPayloadForTopWindow) {
122+
defaultPayloadForTopWindow = data.defaultPayloadForTopWindow;
123+
}
124+
});
125+
126+
const targetOrigin = 'https://local.teams.office.com:8080';
127+
128+
// Utility function to display a response in a given element
129+
const displayResponse = (element, message) => {
130+
element.innerText = message;
131+
};
132+
133+
// Functions to set default payloads
134+
function setDefaultPayloadForBridge() {
135+
const inputElement = document.getElementById('input_childPayloadForBridge');
136+
if (inputElement) {
137+
inputElement.value = defaultPayloadForBridge;
138+
}
139+
}
140+
141+
function setDefaultPayloadForTopWindow() {
142+
const inputElement = document.getElementById('input_childPayloadForTopWindow');
143+
if (inputElement) {
144+
inputElement.value = defaultPayloadForTopWindow;
145+
}
146+
}
147+
148+
// Send Message to NestedAppAuthBridge
149+
function sendMessageToBridge() {
150+
const inputElement = document.getElementById('input_childPayloadForBridge');
151+
const responseElement = document.getElementById('text_bridge_response_for_child');
152+
153+
if (!inputElement || !responseElement) return;
154+
155+
try {
156+
// Validate and send the payload
157+
const payload = JSON.parse(inputElement.value);
158+
159+
// Inline validation for NestedAppAuthRequest
160+
if (!payload) throw new Error('Input is required.');
161+
if (payload.messageType !== 'NestedAppAuthRequest') {
162+
throw new Error('Invalid or missing messageType. Expected "NestedAppAuthRequest".');
163+
}
164+
if (!payload.method) throw new Error('Method name is required in payload.');
165+
if (!payload.requestId) throw new Error('RequestId is required in payload.');
166+
167+
const bridge = window.nestedAppAuthBridge;
168+
if (!bridge) {
169+
displayResponse(responseElement, 'NAA Bridge not available');
170+
return;
171+
}
172+
173+
bridge.addEventListener('message', (response) => {
174+
displayResponse(responseElement, JSON.stringify(response));
175+
});
176+
177+
bridge.postMessage(JSON.stringify(payload));
178+
displayResponse(responseElement, 'Message sent to Bridge. Awaiting response...');
179+
} catch (error) {
180+
console.error('Validation error:', error.message);
181+
displayResponse(responseElement, `Validation Error: ${error.message}`);
182+
}
183+
}
184+
185+
// Send Message to Top Window
186+
function sendMessageToTopWindow() {
187+
const inputElement = document.getElementById('input_childPayloadForTopWindow');
188+
const responseElement = document.getElementById('text_topWindow_response_for_child');
189+
190+
if (!inputElement || !responseElement) return;
191+
192+
try {
193+
// Validate and send the payload
194+
const payload = JSON.parse(inputElement.value);
195+
196+
// Inline validation for Top Window
197+
if (!payload) throw new Error('Input is required.');
198+
if (!payload.id) throw new Error('"id" is required.');
199+
if (!payload.func) throw new Error('"func" is required.');
200+
if (!payload.data) throw new Error('"data" is required with NAA payload.');
201+
202+
// Validate nested NAA payload in "data"
203+
const nestedData = JSON.parse(payload.data);
204+
if (nestedData.messageType !== 'NestedAppAuthRequest') {
205+
throw new Error('Invalid or missing messageType in NAA payload. Expected "NestedAppAuthRequest".');
206+
}
207+
208+
if (!window.top) {
209+
displayResponse(responseElement, 'Top window not accessible');
210+
return;
211+
}
212+
213+
window.addEventListener('message', (event) => {
214+
if (event.origin !== targetOrigin) {
215+
console.warn('Received message from unexpected origin:', event.origin);
216+
return;
217+
}
218+
displayResponse(responseElement, JSON.stringify(event.data));
219+
});
220+
221+
window.top.postMessage(payload, targetOrigin);
222+
displayResponse(responseElement, 'Message sent to Top Window. Awaiting response...');
223+
} catch (error) {
224+
console.error('Validation error:', error.message);
225+
displayResponse(responseElement, `Validation Error: ${error.message}`);
226+
}
227+
}
228+
</script>
229+
</body>
230+
</html>

0 commit comments

Comments
 (0)