fix: Resolve WebSocket authentication error, add verifyJWT alias, and implement 20s heartbeat keep-alive for permanent stable connections

This commit is contained in:
Hamza-Ayed
2026-08-27 03:39:57 +03:00
parent 596f5e6603
commit b11a4bd8c1
4 changed files with 53 additions and 10 deletions
+8
View File
@@ -194,6 +194,14 @@ class Security
return $payload;
}
/**
* Alias for verifyJWT for backward compatibility
*/
public static function decodeJwt(string $token)
{
return self::verifyJWT($token);
}
private static function base64UrlEncode(string $data): string
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
+22 -3
View File
@@ -877,21 +877,34 @@ class StudentPortal
return `${m}:${s}`;
}
let wsPingInterval = null;
// 1. Initialize Real-time WebSocket (Workerman)
function initWebSocket(token) {
if (!token) return;
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//${window.location.host}/ws`;
if (wsSocket && (wsSocket.readyState === WebSocket.OPEN || wsSocket.readyState === WebSocket.CONNECTING)) {
return;
}
try {
wsSocket = new WebSocket(wsUrl);
wsSocket.onopen = () => {
updateWsStatus(true);
wsSocket.send(JSON.stringify({
event: 'auth',
data: { token: token }
}));
// Heartbeat keep-alive every 20s
clearInterval(wsPingInterval);
wsPingInterval = setInterval(() => {
if (wsSocket && wsSocket.readyState === WebSocket.OPEN) {
wsSocket.send(JSON.stringify({ event: 'ping' }));
}
}, 20000);
};
wsSocket.onmessage = (event) => {
@@ -905,7 +918,11 @@ class StudentPortal
wsSocket.onclose = () => {
updateWsStatus(false);
setTimeout(() => initWebSocket(token), 3000);
clearInterval(wsPingInterval);
setTimeout(() => {
const currentToken = localStorage.getItem('saqel_student_jwt');
if (currentToken) initWebSocket(currentToken);
}, 5000);
};
wsSocket.onerror = (err) => {
@@ -933,7 +950,9 @@ class StudentPortal
const event = msg.event;
const data = msg.data;
if (event === 'chat_message' || event === 'chat_sent') {
if (event === 'authenticated') {
updateWsStatus(true);
} else if (event === 'chat_message' || event === 'chat_sent') {
appendChatMessage(data);
} else if (event === 'drm_session_terminated') {
alert('⚠️ تم فتح هذا الحساب من متصفح آخر.');
+22 -6
View File
@@ -556,22 +556,34 @@ class TeacherPortal
}
});
// 1. Initialize Real-time WebSocket (Workerman)
let wsPingInterval = null;
function initWebSocket(token) {
if (!token) return;
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//${window.location.host}/ws`;
if (wsSocket && (wsSocket.readyState === WebSocket.OPEN || wsSocket.readyState === WebSocket.CONNECTING)) {
return;
}
try {
wsSocket = new WebSocket(wsUrl);
wsSocket.onopen = () => {
console.log('⚡ Connected to Saqel Workerman Gateway');
updateWsStatus(true);
wsSocket.send(JSON.stringify({
event: 'auth',
data: { token: token }
}));
// Heartbeat keep-alive every 20s
clearInterval(wsPingInterval);
wsPingInterval = setInterval(() => {
if (wsSocket && wsSocket.readyState === WebSocket.OPEN) {
wsSocket.send(JSON.stringify({ event: 'ping' }));
}
}, 20000);
};
wsSocket.onmessage = (event) => {
@@ -585,12 +597,14 @@ class TeacherPortal
wsSocket.onclose = () => {
updateWsStatus(false);
// Reconnect after 3s
setTimeout(() => initWebSocket(token), 3000);
clearInterval(wsPingInterval);
setTimeout(() => {
const currentToken = localStorage.getItem('saqel_teacher_jwt');
if (currentToken) initWebSocket(currentToken);
}, 5000);
};
wsSocket.onerror = (err) => {
console.warn('WS Error:', err);
updateWsStatus(false);
};
} catch (e) {
@@ -616,7 +630,9 @@ class TeacherPortal
const event = msg.event;
const data = msg.data;
if (event === 'chat_message' || event === 'chat_sent') {
if (event === 'authenticated') {
updateWsStatus(true);
} else if (event === 'chat_message' || event === 'chat_sent') {
appendChatMessage(data);
loadConversations(); // refresh sidebar
} else if (event === 'user_typing') {
+1 -1
View File
@@ -145,7 +145,7 @@ $wsWorker->onMessage = function (TcpConnection $connection, $data) use (&$userCo
}
try {
$decoded = \App\Core\Security::decodeJwt($token);
$decoded = \App\Core\Security::verifyJWT($token);
if (!$decoded || empty($decoded['user_id'])) {
$connection->send(json_encode(['event' => 'auth_error', 'message' => 'Invalid or expired token']));
return;