Code: Select all
const wsUrl = "ws://localhost:8555";
let ws, pc, dc;
let myKey = null;
let myName = "";
let myPhone = "";
let myRoom = "main";
let currentTarget = null;
let map, markerCluster, mapRefreshInterval = null;
let pendingCandidates = [];
let connectionTimeout = null;
const config = {iceServers: [{ urls: "stun:stun.l.google.com:19302" }]};
Code: Select all
async function createPC(isInitiator = false, targetKey = null) {
pc = new RTCPeerConnection(config);
pc.onicecandidate = e => e.candidate &&
ws.send(JSON.stringify({ action: "ice", data: e.candidate, target: targetKey, from: myKey }));
pc.ondatachannel = e => { dc = e.channel; setupDC(); };
pc.onconnectionstatechange = () => {
if (pc.connectionState === "connected") clearTimeout(connectionTimeout);
else if (pc.connectionState === "failed") updateConnectionStatus("failed");
else if (pc.connectionState === "disconnected") updateConnectionStatus("disconnected");
};
if (isInitiator) {
dc = pc.createDataChannel("chat");
setupDC();
}
}
function setupDC() {
dc.onopen = () => {
updateConnectionStatus("connected", currentTarget);
appendMsg("✅ تم إنشاء اتصال مباشر", "other");
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
};
dc.onmessage = e => appendMsg(e.data, "other");
dc.onclose = () => updateConnectionStatus("disconnected");
dc.onerror = () => appendMsg("❌ خطأ في قناة الاتصال", "other");
}
async function startCall(targetKey) {
if (pc) pc.close();
if (dc) dc.close();
pendingCandidates = [];
currentTarget = targetKey.trim().toLowerCase();
updateConnectionStatus("connecting", currentTarget);
appendMsg(`🔄 جاري الاتصال بـ ${currentTarget}...`, "other");
connectionTimeout = setTimeout(() => {
if (!dc || dc.readyState !== "open") {
appendMsg("⏱️ انتهت مهلة الاتصال.", "other");
updateConnectionStatus("failed");
pc?.close();
}
}, 30000);
await createPC(true, currentTarget);
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({ action: "offer", data: pc.localDescription, target: currentTarget, from: myKey }));
}
async function handleOffer(msg) {
currentTarget = msg.from;
showIncomingCallNotification(currentTarget);
playNotificationSound();
await createPC(false, currentTarget);
await pc.setRemoteDescription(msg.data);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
ws.send(JSON.stringify({ action: "answer", data: pc.localDescription, target: currentTarget, from: myKey }));
}
Code: Select all
function connectWebSocket() {
ws = new WebSocket(wsUrl);
ws.onopen = () => {
document.getElementById("status").textContent = "✅ متصل بالخادم";
registerUser();
};
ws.onmessage = async (ev) => {
const msg = JSON.parse(ev.data);
switch (msg.action) {
case "registered":
break;
case "user-list":
renderUsers(msg.users);
break;
case "all-users":
renderMapUsers(msg.users);
break;
case "offer":
await handleOffer(msg);
break;
case "answer":
await pc.setRemoteDescription(msg.data);
for (const c of pendingCandidates) await pc.addIceCandidate(c);
pendingCandidates = [];
break;
case "ice":
if (pc && pc.remoteDescription)
await pc.addIceCandidate(msg.data);
else pendingCandidates.push(msg.data);
break;
case "user-info":
renderSearchResults([msg.data]);
break;
case "search-results":
renderSearchResults(msg.users);
break;
}
};
ws.onclose = () => {
document.getElementById("status").textContent = "❌ غير متصل";
setTimeout(() => {
if (sessionStorage.getItem("userData")) connectWebSocket();
}, 3000);
};
}
async function registerUser() {
const location = await getGeo();
ws.send(JSON.stringify({ action: "register", name: myName, phone: myPhone, room: myRoom, location }));
}
Code: Select all
import asyncio
import json
import os
import time
import websockets
# -----------------------------
# In-memory state
# -----------------------------
# USERS_BY_KEY: key -> { name, phone, room, ip, location, ws, last_seen }
USERS_BY_KEY = {}
# WS_TO_KEY: ws -> key
WS_TO_KEY = {}
DATA_DIR = os.path.abspath(".") # where we store per-user friends_*.json
def user_key_from(name: str, phone: str) -> str:
k = (phone or name or "").strip().lower()
return k
def friends_file_for(key: str) -> str:
safe = "".join(ch for ch in key if ch.isalnum() or ch in ("_", "-", "."))
return os.path.join(DATA_DIR, f"friends_{safe}.json")
def load_friends(key: str):
path = friends_file_for(key)
if not os.path.exists(path):
return []
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, list):
# normalize to lowercase unique strings
seen, out = set(), []
for x in data:
t = (x or "").strip().lower()
if t and t not in seen:
seen.add(t)
out.append(t)
return out
except Exception:
pass
return []
def save_friends(key: str, friends_list):
# normalize and persist
seen, out = set(), []
for x in friends_list:
t = (x or "").strip().lower()
if t and t not in seen:
seen.add(t)
out.append(t)
path = friends_file_for(key)
with open(path, "w", encoding="utf-8") as f:
json.dump(out, f, ensure_ascii=False, indent=2)
return out
async def broadcast_user_list(room: str):
"""Send the list of online users in 'room' to everyone in that room."""
users_payload = []
now = time.time()
for key, info in USERS_BY_KEY.items():
if info.get("room") == room and info.get("ws") is not None:
users_payload.append({
"name": info.get("name"),
"phone": info.get("phone"),
"ip": info.get("ip"),
"room": info.get("room"),
"location": info.get("location"),
"last_seen": info.get("last_seen", now),
})
msg = json.dumps({"action": "user-list", "users": users_payload})
tasks = []
for key, info in USERS_BY_KEY.items():
if info.get("room") == room and info.get("ws") is not None:
tasks.append(info["ws"].send(msg))
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
async def handle_register(ws, data):
name = (data.get("name") or "").strip()
phone = (data.get("phone") or "").strip()
room = (data.get("room") or "main").strip() or "main"
location = data.get("location") # {lat, lon} or None
key = user_key_from(name, phone)
if not key:
await ws.send(json.dumps({"action": "error", "error": "Missing name/phone"}))
return
ip = ws.remote_address[0] if ws.remote_address else "unknown"
USERS_BY_KEY[key] = {
"name": name,
"phone": phone,
"room": room,
"ip": ip,
"location": location,
"ws": ws,
"last_seen": time.time(),
}
WS_TO_KEY[ws] = key
await ws.send(json.dumps({"action": "registered", "key": key, "ip": ip, "room": room}))
await broadcast_user_list(room)
async def handle_get_friends(ws):
key = WS_TO_KEY.get(ws)
if not key:
await ws.send(json.dumps({"action": "friends", "friends": []}))
return
friends = load_friends(key)
await ws.send(json.dumps({"action": "friends", "friends": friends}))
async def handle_friend_add(ws, data):
key = WS_TO_KEY.get(ws)
if not key:
return
target = (data.get("target") or "").strip().lower()
if not target:
return
current = load_friends(key)
if target not in current:
current.append(target)
current = save_friends(key, current)
await ws.send(json.dumps({"action": "friends", "friends": current, "ok": True}))
async def handle_friend_remove(ws, data):
key = WS_TO_KEY.get(ws)
if not key:
return
target = (data.get("target") or "").strip().lower()
if not target:
return
current = load_friends(key)
current = [x for x in current if x != target]
current = save_friends(key, current)
await ws.send(json.dumps({"action": "friends", "friends": current, "ok": True}))
async def handler(ws):
print("🌐 Client connected")
try:
async for raw in ws:
try:
data = json.loads(raw)
except json.JSONDecodeError:
continue
act = data.get("action")
if act == "register":
await handle_register(ws, data)
elif act == "get-friends":
await handle_get_friends(ws)
elif act == "friend-add":
await handle_friend_add(ws, data)
elif act == "friend-remove":
await handle_friend_remove(ws, data)
elif act == "get-all-users":
# Optional: send all users (for map/debug)
payload = []
for key, info in USERS_BY_KEY.items():
payload.append({
"name": info.get("name"),
"phone": info.get("phone"),
"room": info.get("room"),
"ip": info.get("ip"),
"location": info.get("location"),
"last_seen": info.get("last_seen", time.time()),
})
await ws.send(json.dumps({"action": "all-users", "users": payload}))
elif act == "chat":
sender_key = WS_TO_KEY.get(ws)
if not sender_key:
continue
text = (data.get("text") or "").strip()
room = USERS_BY_KEY.get(sender_key, {}).get("room", "main")
if not text:
continue
msg = json.dumps({
"action": "chat",
"from": sender_key,
"text": text,
"time": time.strftime("%H:%M:%S"),
})
# broadcast to same room
tasks = []
for key, info in USERS_BY_KEY.items():
if info.get("room") == room and info.get("ws"):
tasks.append(info["ws"].send(msg))
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
elif act == "chat-private":
sender_key = WS_TO_KEY.get(ws)
target_key = (data.get("target") or "").strip().lower()
text = (data.get("text") or "").strip()
if not sender_key or not target_key or not text:
continue
payload = json.dumps({
"action": "chat-private",
"from": sender_key,
"to": target_key,
"text": text,
"time": time.strftime("%H:%M:%S"),
})
for k, info in USERS_BY_KEY.items():
if k in (sender_key, target_key) and info.get("ws"):
await info["ws"].send(payload)
except websockets.exceptions.ConnectionClosed:
pass
finally:
# cleanup
key = WS_TO_KEY.pop(ws, None)
if key and key in USERS_BY_KEY:
room = USERS_BY_KEY[key].get("room", "main")
USERS_BY_KEY[key]["ws"] = None
USERS_BY_KEY[key]["last_seen"] = time.time()
print(f"❎ {key} disconnected")
await broadcast_user_list(room)
async def main():
host = "0.0.0.0"
port = 8555
print("📂 Data dir:", DATA_DIR)
print(f"📡 Signaling server on ws://{host}:{port}")
async with websockets.serve(handler, host, port, ping_interval=20, ping_timeout=20):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())
Mobile version