mirror of
https://github.com/xtr-dev/rondevu-demo.git
synced 2025-12-10 02:43:23 +00:00
Compare commits
3 Commits
3a42f74371
...
c511b15fbf
| Author | SHA1 | Date | |
|---|---|---|---|
| c511b15fbf | |||
| 71454e31d1 | |||
| b2a17ce42b |
19
package-lock.json
generated
19
package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "rondevu-demo",
|
"name": "rondevu-demo",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xtr-dev/rondevu-client": "^0.9.2",
|
||||||
"@zxing/library": "^0.21.3",
|
"@zxing/library": "^0.21.3",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -744,6 +745,15 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/ed25519": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-QyteqMNm0GLqfa5SoYbSC3+Pvykwpn95Zgth4MFVSMKBB75ELl9tX1LAVsN4c3HXOrakHsF2gL4zWDAYCcsnzg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.27",
|
"version": "1.0.0-beta.27",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||||
@@ -1160,6 +1170,15 @@
|
|||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xtr-dev/rondevu-client": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-DVow5AOPU40dqQtlfQK7J2GNX8dz2/4UzltMqublaPZubbkRYgocvp0b76NQu5F6v150IstMV2N49uxAYqogVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/ed25519": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@zxing/library": {
|
"node_modules/@zxing/library": {
|
||||||
"version": "0.21.3",
|
"version": "0.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo"
|
"deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xtr-dev/rondevu-client": "^0.9.2",
|
||||||
"@zxing/library": "^0.21.3",
|
"@zxing/library": "^0.21.3",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
300
src/App.jsx
300
src/App.jsx
@@ -4,22 +4,72 @@ import toast, { Toaster } from 'react-hot-toast';
|
|||||||
|
|
||||||
const API_URL = 'https://api.ronde.vu';
|
const API_URL = 'https://api.ronde.vu';
|
||||||
|
|
||||||
const RTC_CONFIG = {
|
// Preset RTC configurations
|
||||||
iceServers: [
|
const RTC_PRESETS = {
|
||||||
{ urls: ["stun:stun.share.fish:3478"] },
|
'ipv4-turn': {
|
||||||
{
|
name: 'IPv4 TURN (Recommended)',
|
||||||
urls: [
|
config: {
|
||||||
// TURNS (secure) - TLS/DTLS on port 5349
|
iceServers: [
|
||||||
"turns:turn.share.fish:5349?transport=tcp",
|
{ urls: ["stun:57.129.61.67:3478"] },
|
||||||
"turns:turn.share.fish:5349?transport=udp",
|
{
|
||||||
// TURN (fallback) - plain on port 3478
|
urls: [
|
||||||
"turn:turn.share.fish:3478?transport=tcp",
|
"turn:57.129.61.67:3478?transport=tcp",
|
||||||
"turn:turn.share.fish:3478?transport=udp",
|
"turn:57.129.61.67:3478?transport=udp",
|
||||||
|
],
|
||||||
|
username: "webrtcuser",
|
||||||
|
credential: "supersecretpassword"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
username: "webrtcuser",
|
|
||||||
credential: "supersecretpassword"
|
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
|
'hostname-turns': {
|
||||||
|
name: 'Hostname TURNS (TLS)',
|
||||||
|
config: {
|
||||||
|
iceServers: [
|
||||||
|
{ urls: ["stun:turn.share.fish:3478"] },
|
||||||
|
{
|
||||||
|
urls: [
|
||||||
|
"turns:turn.share.fish:5349?transport=tcp",
|
||||||
|
"turns:turn.share.fish:5349?transport=udp",
|
||||||
|
"turn:turn.share.fish:3478?transport=tcp",
|
||||||
|
"turn:turn.share.fish:3478?transport=udp",
|
||||||
|
],
|
||||||
|
username: "webrtcuser",
|
||||||
|
credential: "supersecretpassword"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'google-stun': {
|
||||||
|
name: 'Google STUN Only',
|
||||||
|
config: {
|
||||||
|
iceServers: [
|
||||||
|
{ urls: 'stun:stun.l.google.com:19302' },
|
||||||
|
{ urls: 'stun:stun1.l.google.com:19302' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'relay-only': {
|
||||||
|
name: 'Force TURN Relay (Testing)',
|
||||||
|
config: {
|
||||||
|
iceServers: [
|
||||||
|
{ urls: ["stun:57.129.61.67:3478"] },
|
||||||
|
{
|
||||||
|
urls: [
|
||||||
|
"turn:57.129.61.67:3478?transport=tcp",
|
||||||
|
"turn:57.129.61.67:3478?transport=udp",
|
||||||
|
],
|
||||||
|
username: "webrtcuser",
|
||||||
|
credential: "supersecretpassword"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
iceTransportPolicy: 'relay'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'custom': {
|
||||||
|
name: 'Custom Configuration',
|
||||||
|
config: null // Will be loaded from user input
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@@ -45,6 +95,48 @@ export default function App() {
|
|||||||
const [serviceHandle, setServiceHandle] = useState(null);
|
const [serviceHandle, setServiceHandle] = useState(null);
|
||||||
const chatEndRef = useRef(null);
|
const chatEndRef = useRef(null);
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
|
const [rtcPreset, setRtcPreset] = useState('ipv4-turn');
|
||||||
|
const [customRtcConfig, setCustomRtcConfig] = useState('');
|
||||||
|
|
||||||
|
// Get current RTC configuration
|
||||||
|
const getCurrentRtcConfig = () => {
|
||||||
|
if (rtcPreset === 'custom') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(customRtcConfig);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Invalid custom RTC config:', err);
|
||||||
|
return RTC_PRESETS['ipv4-turn'].config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RTC_PRESETS[rtcPreset]?.config || RTC_PRESETS['ipv4-turn'].config;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load saved settings
|
||||||
|
useEffect(() => {
|
||||||
|
const savedPreset = localStorage.getItem('rondevu-rtc-preset');
|
||||||
|
const savedCustomConfig = localStorage.getItem('rondevu-rtc-custom');
|
||||||
|
|
||||||
|
if (savedPreset) {
|
||||||
|
setRtcPreset(savedPreset);
|
||||||
|
}
|
||||||
|
if (savedCustomConfig) {
|
||||||
|
setCustomRtcConfig(savedCustomConfig);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Save settings when they change
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('rondevu-rtc-preset', rtcPreset);
|
||||||
|
}, [rtcPreset]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (customRtcConfig) {
|
||||||
|
localStorage.setItem('rondevu-rtc-custom', customRtcConfig);
|
||||||
|
}
|
||||||
|
}, [customRtcConfig]);
|
||||||
|
|
||||||
// Load saved data and auto-register
|
// Load saved data and auto-register
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedCreds = localStorage.getItem('rondevu-chat-credentials');
|
const savedCreds = localStorage.getItem('rondevu-chat-credentials');
|
||||||
@@ -194,7 +286,7 @@ export default function App() {
|
|||||||
ttl: 300000, // 5 minutes - service auto-refreshes
|
ttl: 300000, // 5 minutes - service auto-refreshes
|
||||||
ttlRefreshMargin: 0.2, // Refresh at 80% of TTL
|
ttlRefreshMargin: 0.2, // Refresh at 80% of TTL
|
||||||
poolSize: 10, // Support up to 10 simultaneous connections
|
poolSize: 10, // Support up to 10 simultaneous connections
|
||||||
rtcConfig: RTC_CONFIG,
|
rtcConfig: getCurrentRtcConfig(),
|
||||||
handler: (channel, connectionId) => {
|
handler: (channel, connectionId) => {
|
||||||
console.log(`📡 New chat connection: ${connectionId}`);
|
console.log(`📡 New chat connection: ${connectionId}`);
|
||||||
|
|
||||||
@@ -334,7 +426,7 @@ export default function App() {
|
|||||||
|
|
||||||
// Create durable connection
|
// Create durable connection
|
||||||
const connection = await client.connect(contact, 'chat.rondevu@1.0.0', {
|
const connection = await client.connect(contact, 'chat.rondevu@1.0.0', {
|
||||||
rtcConfig: RTC_CONFIG,
|
rtcConfig: getCurrentRtcConfig(),
|
||||||
maxReconnectAttempts: 5
|
maxReconnectAttempts: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -491,6 +583,67 @@ export default function App() {
|
|||||||
<div style={styles.container}>
|
<div style={styles.container}>
|
||||||
<Toaster position="top-right" />
|
<Toaster position="top-right" />
|
||||||
|
|
||||||
|
{/* Settings Modal */}
|
||||||
|
{showSettings && (
|
||||||
|
<div style={styles.modalOverlay} onClick={() => setShowSettings(false)}>
|
||||||
|
<div style={styles.modalContent} onClick={(e) => e.stopPropagation()}>
|
||||||
|
<h2 style={styles.modalTitle}>WebRTC Configuration</h2>
|
||||||
|
|
||||||
|
<div style={styles.settingsSection}>
|
||||||
|
<label style={styles.settingsLabel}>
|
||||||
|
Preset Configuration:
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={rtcPreset}
|
||||||
|
onChange={(e) => setRtcPreset(e.target.value)}
|
||||||
|
style={styles.settingsSelect}
|
||||||
|
>
|
||||||
|
{Object.entries(RTC_PRESETS).map(([key, preset]) => (
|
||||||
|
<option key={key} value={key}>
|
||||||
|
{preset.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{rtcPreset === 'custom' && (
|
||||||
|
<div style={styles.settingsSection}>
|
||||||
|
<label style={styles.settingsLabel}>
|
||||||
|
Custom RTC Configuration (JSON):
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={customRtcConfig}
|
||||||
|
onChange={(e) => setCustomRtcConfig(e.target.value)}
|
||||||
|
placeholder={JSON.stringify(RTC_PRESETS['ipv4-turn'].config, null, 2)}
|
||||||
|
style={styles.settingsTextarea}
|
||||||
|
rows={15}
|
||||||
|
/>
|
||||||
|
<p style={styles.settingsHint}>
|
||||||
|
Enter valid RTCConfiguration JSON
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rtcPreset !== 'custom' && (
|
||||||
|
<div style={styles.settingsSection}>
|
||||||
|
<label style={styles.settingsLabel}>
|
||||||
|
Current Configuration:
|
||||||
|
</label>
|
||||||
|
<pre style={styles.settingsPreview}>
|
||||||
|
{JSON.stringify(getCurrentRtcConfig(), null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={styles.modalActions}>
|
||||||
|
<button onClick={() => setShowSettings(false)} style={styles.modalBtn}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Setup Screen */}
|
{/* Setup Screen */}
|
||||||
{setupStep !== 'ready' && (
|
{setupStep !== 'ready' && (
|
||||||
<div style={styles.setupScreen}>
|
<div style={styles.setupScreen}>
|
||||||
@@ -545,9 +698,14 @@ export default function App() {
|
|||||||
<span style={styles.onlineDot}></span> Online
|
<span style={styles.onlineDot}></span> Online
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleLogout} style={styles.logoutBtn} title="Logout">
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
Logout
|
<button onClick={() => setShowSettings(true)} style={styles.settingsBtn} title="Settings">
|
||||||
</button>
|
⚙️
|
||||||
|
</button>
|
||||||
|
<button onClick={handleLogout} style={styles.logoutBtn} title="Logout">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add Contact */}
|
{/* Add Contact */}
|
||||||
@@ -843,6 +1001,16 @@ const styles = {
|
|||||||
background: '#4caf50',
|
background: '#4caf50',
|
||||||
display: 'inline-block'
|
display: 'inline-block'
|
||||||
},
|
},
|
||||||
|
settingsBtn: {
|
||||||
|
padding: '8px 12px',
|
||||||
|
background: '#3a3a3a',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '18px',
|
||||||
|
lineHeight: '1'
|
||||||
|
},
|
||||||
logoutBtn: {
|
logoutBtn: {
|
||||||
padding: '8px 12px',
|
padding: '8px 12px',
|
||||||
background: '#3a3a3a',
|
background: '#3a3a3a',
|
||||||
@@ -1074,6 +1242,100 @@ const styles = {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
modalOverlay: {
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 1000
|
||||||
|
},
|
||||||
|
modalContent: {
|
||||||
|
background: '#2a2a2a',
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '24px',
|
||||||
|
maxWidth: '600px',
|
||||||
|
width: '90%',
|
||||||
|
maxHeight: '80vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.5)'
|
||||||
|
},
|
||||||
|
modalTitle: {
|
||||||
|
fontSize: '24px',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
marginBottom: '20px',
|
||||||
|
fontWeight: '600'
|
||||||
|
},
|
||||||
|
settingsSection: {
|
||||||
|
marginBottom: '20px'
|
||||||
|
},
|
||||||
|
settingsLabel: {
|
||||||
|
display: 'block',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
marginBottom: '8px',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '500'
|
||||||
|
},
|
||||||
|
settingsSelect: {
|
||||||
|
width: '100%',
|
||||||
|
padding: '10px',
|
||||||
|
background: '#1a1a1a',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
border: '1px solid #3a3a3a',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '14px',
|
||||||
|
outline: 'none',
|
||||||
|
cursor: 'pointer'
|
||||||
|
},
|
||||||
|
settingsTextarea: {
|
||||||
|
width: '100%',
|
||||||
|
padding: '12px',
|
||||||
|
background: '#1a1a1a',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
border: '1px solid #3a3a3a',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
outline: 'none',
|
||||||
|
resize: 'vertical'
|
||||||
|
},
|
||||||
|
settingsPreview: {
|
||||||
|
width: '100%',
|
||||||
|
padding: '12px',
|
||||||
|
background: '#1a1a1a',
|
||||||
|
color: '#4a9eff',
|
||||||
|
border: '1px solid #3a3a3a',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
overflowX: 'auto',
|
||||||
|
margin: 0
|
||||||
|
},
|
||||||
|
settingsHint: {
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#808080',
|
||||||
|
marginTop: '6px'
|
||||||
|
},
|
||||||
|
modalActions: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
gap: '12px',
|
||||||
|
marginTop: '24px'
|
||||||
|
},
|
||||||
|
modalBtn: {
|
||||||
|
padding: '10px 20px',
|
||||||
|
background: '#4a9eff',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user