fix: local IP detection regex + add join code UI when no peers found
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 38s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 38s
The ICE candidate regex was matching the wrong part of the candidate string. Also, iOS Safari blocks local IP detection via WebRTC (mDNS obfuscation), so when no peers are found, show a prominent "create link" button and a join-by-code input for easy cross-network pairing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
99a182a831
commit
2e3408e8d7
@ -1,23 +1,73 @@
|
||||
import { useState } from "react";
|
||||
import { useStore } from "../stores/useStore";
|
||||
import PeerAvatar from "./PeerAvatar";
|
||||
|
||||
interface PeerListProps {
|
||||
onPeerSelect: (peerId: string) => void;
|
||||
onCreateRoom?: () => void;
|
||||
}
|
||||
|
||||
export default function PeerList({ onPeerSelect }: PeerListProps) {
|
||||
export default function PeerList({ onPeerSelect, onCreateRoom }: PeerListProps) {
|
||||
const peers = useStore((s) => s.peers);
|
||||
const selectedPeerId = useStore((s) => s.selectedPeerId);
|
||||
const [joinCode, setJoinCode] = useState("");
|
||||
|
||||
if (peers.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-slate-500">
|
||||
<div className="flex flex-col items-center justify-center py-12 text-slate-500">
|
||||
<div className="text-5xl mb-4">📡</div>
|
||||
<p className="text-lg font-medium">En attente d'appareils...</p>
|
||||
<p className="text-sm mt-2 text-center max-w-xs">
|
||||
Ouvrez AnyDrop sur un autre appareil connecté au même Wi-Fi,
|
||||
ou partagez un lien public.
|
||||
Ouvrez AnyDrop sur un autre appareil connecté au même Wi-Fi.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 w-full max-w-xs space-y-3">
|
||||
<p className="text-xs text-slate-600 text-center">Pas sur le même réseau ?</p>
|
||||
|
||||
{onCreateRoom && (
|
||||
<button
|
||||
onClick={onCreateRoom}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-2.5
|
||||
border border-slate-700 hover:border-brand-500 rounded-xl
|
||||
text-slate-300 hover:text-white transition-all
|
||||
bg-slate-900/30 hover:bg-slate-900/50"
|
||||
>
|
||||
<span>🔗</span>
|
||||
<span className="text-sm font-medium">Créer un lien de partage</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={joinCode}
|
||||
onChange={(e) => setJoinCode(e.target.value.toLowerCase().trim())}
|
||||
placeholder="Code de partage"
|
||||
maxLength={4}
|
||||
className="flex-1 px-3 py-2.5 bg-slate-800/50 border border-slate-700
|
||||
rounded-xl text-white text-sm text-center font-mono tracking-widest
|
||||
placeholder:text-slate-600 focus:outline-none focus:border-brand-500"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && joinCode.length >= 3) {
|
||||
window.location.href = `/${joinCode}`;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (joinCode.length >= 3) {
|
||||
window.location.href = `/${joinCode}`;
|
||||
}
|
||||
}}
|
||||
disabled={joinCode.length < 3}
|
||||
className="px-4 py-2.5 bg-brand-500 hover:bg-brand-400 disabled:opacity-30
|
||||
disabled:cursor-not-allowed rounded-xl text-white text-sm font-medium
|
||||
transition-colors"
|
||||
>
|
||||
Rejoindre
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -26,13 +26,15 @@ export async function detectLocalIP(timeoutMs = 3000): Promise<string | undefine
|
||||
|
||||
pc.onicecandidate = (event) => {
|
||||
if (!event.candidate?.candidate) return;
|
||||
const match = event.candidate.candidate.match(
|
||||
/(?:srflx|host)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s/,
|
||||
);
|
||||
if (match) {
|
||||
const ip = match[1];
|
||||
if (isPrivateIPv4(ip)) {
|
||||
done(ip);
|
||||
// ICE candidate format: "candidate:<foundation> <component> <transport> <priority> <ip> <port> typ <type> ..."
|
||||
// Extract all IPv4 addresses from the candidate string
|
||||
const ips = event.candidate.candidate.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g);
|
||||
if (ips) {
|
||||
for (const ip of ips) {
|
||||
if (isPrivateIPv4(ip)) {
|
||||
done(ip);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -124,7 +124,7 @@ function HomeConnected() {
|
||||
|
||||
{/* Peer list */}
|
||||
<section className="mb-6">
|
||||
<PeerList onPeerSelect={handlePeerSelect} />
|
||||
<PeerList onPeerSelect={handlePeerSelect} onCreateRoom={createPublicRoom} />
|
||||
</section>
|
||||
|
||||
{/* Drop zone */}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user