-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathcsp.recording.html
More file actions
143 lines (122 loc) · 6.04 KB
/
csp.recording.html
File metadata and controls
143 lines (122 loc) · 6.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" nonce="TEST_PAGE_NONCE" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" nonce="TEST_PAGE_NONCE" src="https://unpkg.com/@babel/standalone@7.8.7/babel.min.js"></script>
<script crossorigin="anonymous" nonce="TEST_PAGE_NONCE" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" nonce="TEST_PAGE_NONCE" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" nonce="TEST_PAGE_NONCE" src="/test-harness.js"></script>
<script crossorigin="anonymous" nonce="TEST_PAGE_NONCE" src="/test-page-object.js"></script>
<script crossorigin="anonymous" nonce="TEST_PAGE_NONCE" src="/__dist__/webchat-es5.js"></script>
<script crossorigin="anonymous" nonce="TEST_PAGE_NONCE" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
<!--
CSP Configuration for testing AudioWorklet with blob URLs
Key requirements for AudioWorklet CSP compliance:
- connect-src blob: allows AudioWorklet.addModule() with blob URLs
This test verifies that:
1. AudioWorklet can be loaded via blob URL under strict CSP
2. Recording functionality works with CSP enabled
3. No CSP violations occur during the recording flow
Note: 'unsafe-inline' is used for style-src to allow styles from Fluent package, however our testing focuses on and connect-src directives with blob: which is working fine here.
-->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; base-uri 'none'; connect-src blob: https://directline.botframework.com wss://directline.botframework.com; img-src blob: data:; script-src 'strict-dynamic' 'nonce-TEST_PAGE_NONCE'; style-src 'unsafe-inline'; media-src blob: mediastream:"
/>
</head>
<body>
<main id="webchat"></main>
<script nonce="TEST_PAGE_NONCE" type="module">
import { setupMockMediaDevices } from '/assets/esm/speechToSpeech/mockMediaDevices.js';
// Setup mock media devices before test starts
setupMockMediaDevices();
</script>
<script nonce="TEST_PAGE_NONCE" type="text/babel">
run(async function () {
const {
React,
ReactDOM: { render },
WebChat: { FluentThemeProvider, ReactWebChat, testIds }
} = window;
// Track CSP violations
const cspViolations = [];
document.addEventListener('securitypolicyviolation', (e) => {
cspViolations.push({
violatedDirective: e.violatedDirective,
blockedURI: e.blockedURI,
originalPolicy: e.originalPolicy
});
console.error('CSP Violation:', e.violatedDirective, e.blockedURI);
});
// Verify CSP is active
const cspMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
expect(cspMeta).toBeTruthy();
expect(cspMeta.content).toContain('connect-src blob:');
// GIVEN: Web Chat with Speech-to-Speech enabled and CSP headers
const { directLine, store } = testHelpers.createDirectLineEmulator();
// Multi-modal experience: server announces audio, consumer opted into voice mode.
directLine.setCapability('getVoiceConfiguration', { sampleRate: 24000, chunkIntervalMs: 100 }, { emitEvent: false });
directLine.setCapability('getIsVoiceModeEnabled', true, { emitEvent: false });
render(
<FluentThemeProvider variant="fluent">
<ReactWebChat
directLine={directLine}
store={store}
nonce="WEB_CHAT_NONCE"
styleOptions={{
// TODO: Use blob url instead of raw data URI and remove this workaround
voiceProcessingSound: false
}}
/>
</FluentThemeProvider>,
document.getElementById('webchat')
);
await pageConditions.uiConnected();
const micButton = document.querySelector(`[data-testid="${testIds.sendBoxMicrophoneButton}"]`);
expect(micButton).toBeTruthy();
// WHEN: User clicks microphone button to start recording
// This triggers AudioWorklet initialization with blob URL
await host.click(micButton);
// THEN: Button should show recording state (AudioWorklet loaded successfully via blob URL)
await pageConditions.became(
'Microphone button changes to recording state',
() => {
const label = micButton.getAttribute('aria-label');
return label && label.includes('Microphone on');
},
1000
);
// THEN: Verify no CSP violations occurred during AudioWorklet loading
const blobViolations = cspViolations.filter(v =>
v.blockedURI.startsWith('blob:') && v.violatedDirective.includes('connect-src')
);
expect(blobViolations.length).toBe(0);
// THEN: Verify recording state is active (AudioWorklet is functioning)
await pageConditions.became(
'Voice state is listening (recording active)',
() => store.getState().voice?.voiceState === 'listening',
1000
);
// WHEN: User stops recording
await host.click(micButton);
// THEN: Button should change to not-recording state
await pageConditions.became(
'Microphone button changes to not-recording state',
() => {
const label = micButton.getAttribute('aria-label');
return label && label.includes('Microphone off');
},
1000
);
// Final verification: No blob-related CSP violations
const finalBlobViolations = cspViolations.filter(v =>
v.blockedURI.startsWith('blob:')
);
if (finalBlobViolations.length > 0) {
console.error('CSP blob violations detected:', finalBlobViolations);
}
expect(finalBlobViolations.length).toBe(0);
});
</script>
</body>
</html>