chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل

نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق».
نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا
`cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules ·
.dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore.

هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً
مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ.

⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا
صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم
توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:10:29 +03:00
co-authored by Claude Opus 5
commit 92dc6b3641
3654 changed files with 896478 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"require": {
"firebase/php-jwt": "^7.0",
"workerman/phpsocket.io": "^2.2"
}
}
+239
View File
@@ -0,0 +1,239 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "59d0362abeac10ecb395ae46536e8a08",
"packages": [
{
"name": "firebase/php-jwt",
"version": "v7.0.2",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/5645b43af647b6947daac1d0f659dd1fbe8d3b65",
"reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.4",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"psr/cache": "^2.0||^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-sodium": "Support EdDSA (Ed25519) signatures",
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v7.0.2"
},
"time": "2025-12-16T22:17:28+00:00"
},
{
"name": "workerman/channel",
"version": "v1.2.3",
"source": {
"type": "git",
"url": "https://github.com/walkor/channel.git",
"reference": "5edb0008eae35bf2da7218d911042abd23aa4370"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/channel/zipball/5edb0008eae35bf2da7218d911042abd23aa4370",
"reference": "5edb0008eae35bf2da7218d911042abd23aa4370",
"shasum": ""
},
"require": {
"workerman/workerman": ">=4.0.12"
},
"type": "library",
"autoload": {
"psr-4": {
"Channel\\": "./src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"homepage": "http://www.workerman.net",
"support": {
"issues": "https://github.com/walkor/channel/issues",
"source": "https://github.com/walkor/channel/tree/v1.2.3"
},
"time": "2025-07-08T01:33:22+00:00"
},
{
"name": "workerman/phpsocket.io",
"version": "v2.2.2",
"source": {
"type": "git",
"url": "https://github.com/walkor/phpsocket.io.git",
"reference": "5f96eace9f2bcec82555a97ac9867b732024d3b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/phpsocket.io/zipball/5f96eace9f2bcec82555a97ac9867b732024d3b6",
"reference": "5f96eace9f2bcec82555a97ac9867b732024d3b6",
"shasum": ""
},
"require": {
"ext-json": "*",
"workerman/channel": ">=1.0.0",
"workerman/workerman": "^4.0.0"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"PHPSocketIO\\": "./src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A server side alternative implementation of socket.io in PHP based on Workerman",
"homepage": "https://www.workerman.net",
"keywords": [
"Socket.io",
"async",
"non-blocking",
"phpsocket.io",
"server",
"sockets",
"stream",
"workerman"
],
"support": {
"issues": "https://github.com/walkor/phpsocket.io/issues",
"source": "https://github.com/walkor/phpsocket.io/tree/v2.2.2"
},
"funding": [
{
"url": "https://opencollective.com/walkor",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/walkor",
"type": "patreon"
}
],
"time": "2025-04-01T08:36:37+00:00"
},
{
"name": "workerman/workerman",
"version": "v4.2.1",
"source": {
"type": "git",
"url": "https://github.com/walkor/workerman.git",
"reference": "cafb5a43d93d7d30a16b32a57948581cca993562"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/workerman/zipball/cafb5a43d93d7d30a16b32a57948581cca993562",
"reference": "cafb5a43d93d7d30a16b32a57948581cca993562",
"shasum": ""
},
"require": {
"php": ">=8.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"type": "library",
"autoload": {
"psr-4": {
"Workerman\\": "./"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
],
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"homepage": "http://www.workerman.net",
"keywords": [
"asynchronous",
"event-loop"
],
"support": {
"email": "walkor@workerman.net",
"forum": "http://wenda.workerman.net/",
"issues": "https://github.com/walkor/workerman/issues",
"source": "https://github.com/walkor/workerman",
"wiki": "http://doc.workerman.net/"
},
"funding": [
{
"url": "https://opencollective.com/workerman",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/walkor",
"type": "patreon"
}
],
"time": "2024-11-24T11:45:37+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}
+3
View File
@@ -0,0 +1,3 @@
<?php
echo 'Hello World :-)';
+65
View File
@@ -0,0 +1,65 @@
<?php
// ride_server/intaleq/connect.php
// Bootstrap for standalone REST endpoints under ride_server/intaleq/.
// Modeled on loction_server/siro/connect.php (the JWT-enforcing variant).
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/load_env.php';
// .env path mirrors ride_server/passenger_socket.php's own already-deployed
// convention on this box — confirm/adjust with ops at deploy time.
$env_file = file_exists(__DIR__ . '/../ride-keys/.env')
? __DIR__ . '/../ride-keys/.env'
: (file_exists('/home/intaleq-rides/env/.env') ? '/home/intaleq-rides/env/.env' : '');
if (!empty($env_file)) {
loadEnvironment($env_file);
}
require_once __DIR__ . '/functions.php';
// --- CORS + JSON headers ---
header("Access-Control-Allow-Origin: https://intaleqapp.com");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header('Content-Type: application/json');
date_default_timezone_set('Asia/Amman');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
// --- JWT auth — enforced. This endpoint returns ride status tied to a
// specific passenger/driver, and backend's connect.php enforces JWT for the
// same call today; dropping auth here would be a regression. ---
$decodedToken = authenticateJWT();
$user_id = $decodedToken->sub ?? $decodedToken->user_id ?? null;
// --- DB connection: same physical database backend's Database::get('main')
// resolves to (confirmed distinct from Database::get('ride') — the getRideStatus
// endpoint this replaces has always read from 'main', so the fallback query
// below stays on 'main' too, for zero behavior change vs. today). Env var
// names match backend/core/Database/Database.php's 'main' entry exactly;
// falling back to loction_server's simpler names only if ops sets this box
// up differently. Actual values must be confirmed/provisioned by ops. ---
$dbname = getenv('DB_PRIMARY_NAME_V2') ?: getenv('dbname');
$dbhost = getenv('DB_PRIMARY_HOST_V2') ?: 'localhost';
$dbuser = getenv('DB_PRIMARY_USER_V2') ?: getenv('USER');
$dbpass = getenv('DB_PRIMARY_PASS_V2') ?: getenv('PASS');
try {
$dsn = "mysql:host=$dbhost;dbname=$dbname;charset=utf8mb4";
$con = new PDO($dsn, $dbuser, $dbpass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8",
PDO::ATTR_TIMEOUT => 5,
]);
} catch (PDOException $e) {
error_log("[ride_server/connect] DB connection failed: " . $e->getMessage());
http_response_code(500);
echo json_encode(['status' => 'failure', 'message' => 'A database error occurred.']);
exit;
}
+95
View File
@@ -0,0 +1,95 @@
<?php
// ride_server/intaleq/functions.php
// Bootstrap helpers for standalone REST endpoints under ride_server/intaleq/.
// Mirrors the env/key conventions already deployed on this box for
// ride_server/passenger_socket.php (/home/intaleq-rides/...), not invented.
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
// Shared secret used to authenticate to loction_server's internal HTTP API —
// same mechanism ride_server/passenger_socket.php already uses on this box.
function getInternalSocketKey(): string {
$key = getenv('INTERNAL_SOCKET_KEY');
if ($key) return trim($key);
$path = getenv('INTERNAL_SOCKET_KEY_PATH') ?: '/home/intaleq-rides/.internal_socket_key';
if (file_exists($path)) return trim((string) @file_get_contents($path));
return '';
}
// JWT signing secret — must resolve to the same value backend's
// core/Auth/JwtService.php signs tokens with, or every token fails here.
function getJwtSecret(): string {
$keyPath = getenv('JWT_SECRET_KEY_PATH');
if ($keyPath && file_exists($keyPath)) {
return trim(file_get_contents($keyPath));
}
return getenv('JWT_SECRET_KEY') ?: '';
}
function authenticateJWT() {
$secretKey = getJwtSecret();
if (!$secretKey) {
error_log("[ride_server] JWT secret not configured.");
http_response_code(500);
echo json_encode(['status' => 'failure', 'message' => 'Internal server configuration error.']);
exit;
}
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = null;
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
$token = $matches[1];
}
if (!$token) {
http_response_code(401);
echo json_encode(['status' => 'failure', 'message' => 'Authorization token required']);
exit;
}
try {
return JWT::decode($token, new Key($secretKey, 'HS256'));
} catch (ExpiredException $e) {
http_response_code(401);
echo json_encode(['status' => 'failure', 'message' => 'Token expired']);
exit;
} catch (SignatureInvalidException $e) {
http_response_code(401);
echo json_encode(['status' => 'failure', 'message' => 'Invalid token signature']);
exit;
} catch (BeforeValidException $e) {
http_response_code(401);
echo json_encode(['status' => 'failure', 'message' => 'Token not yet valid']);
exit;
} catch (Exception $e) {
http_response_code(401);
echo json_encode(['status' => 'failure', 'message' => 'Invalid token']);
exit;
}
// NOTE: signature/expiry only — does not replicate backend's
// core/Auth/JwtService::authenticate() JTI-blacklist (revoked-token)
// check or X-Device-FP verification. Flagged as a follow-up in the
// plan; not blocking for this read-only endpoint.
}
// Flutter's CRUD().get() always issues an HTTP POST under the hood
// (see siro_rider/lib/controller/functions/crud.dart _makeRequest/doPost) —
// so every field this endpoint reads comes through $_POST, never $_GET.
function filterRequest($requestname, $type = 'string') {
if (isset($_POST[$requestname]) && $_POST[$requestname] !== '') {
$value = trim($_POST[$requestname]);
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value);
if ($type === 'numeric') {
return filter_var($value, FILTER_VALIDATE_FLOAT) !== false ? $value : null;
}
return $value;
}
return null;
}
function printFailure($message = "none") {
echo json_encode(["status" => "failure", "message" => $message]);
}
+23
View File
@@ -0,0 +1,23 @@
<?php
function loadEnvironment($env_file) {
if (!file_exists($env_file)) {
error_log("❌ .env not found: $env_file");
return false;
}
$lines = file($env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || strpos($line, '#') === 0) continue;
$parts = explode('=', $line, 2);
if (count($parts) === 2) {
[$keyName, $value] = $parts;
$value = trim($value, "\"'");
putenv("$keyName=$value");
$_ENV[$keyName] = $value;
$_SERVER[$keyName] = $value;
}
}
return true;
}
@@ -0,0 +1,88 @@
<?php
// ride_server/intaleq/ride/rides/getRideStatus.php
// Serves https://rides.intaleq.xyz/intaleq/ride/rides/getRideStatus.php
// Same response contract as the legacy backend/ride/rides/getRideStatus.php:
// {"status":"success","data":"<ride status string>"}
//
// Reads Redis truth via loction_server's internal HTTP API first (same
// "ask location server, fall back to DB" shape as backend/ride/location/get.php);
// on any miss/timeout/error, falls back to a direct MySQL SELECT so behavior
// never regresses to "no answer."
require_once __DIR__ . '/../../connect.php';
$id = filterRequest("id");
$rideId = (int) $id;
if (empty($id) || $rideId <= 0) {
printFailure("Missing ride ID.");
exit;
}
// ── 1. Try Redis via the location server's internal HTTP API ──────────────
$status = null;
$locationServerUrl = getenv('LOCATION_SERVER_URL') ?: 'http://nginx/loction_server/driver_socket.php';
$internalKey = getInternalSocketKey();
if ($internalKey) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $locationServerUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'action' => 'get_ride_state',
'ride_id' => $rideId,
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Foreground/synchronous read — the answer IS the response, so this is
// deliberately longer than the 200-500ms fire-and-forget write timeouts
// used elsewhere in this codebase.
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 500);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 1500);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $internalKey"]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if (!$curlErr && $httpCode === 200 && $response) {
$json = json_decode($response, true);
if (is_array($json) && ($json['status'] ?? false) === true
&& !empty($json['data']['status'])) {
$status = $json['data']['status'];
}
} else {
error_log("[getRideStatus] location-server read miss/error for ride=$rideId: "
. ($curlErr ?: "HTTP $httpCode"));
}
} else {
error_log("[getRideStatus] internal key not configured — skipping Redis read, using DB fallback.");
}
// ── 2. Fallback: direct MySQL read (identical query to the legacy file) ───
if ($status === null) {
try {
$stmt = $con->prepare("SELECT `status` FROM `ride` WHERE `id` = :id");
$stmt->bindParam(':id', $rideId, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row && isset($row['status'])) {
$status = $row['status'];
}
} catch (PDOException $e) {
error_log("[getRideStatus] DB fallback error for ride=$rideId: " . $e->getMessage());
http_response_code(500);
echo json_encode(["status" => "failure", "message" => "An internal error occurred."]);
exit;
}
}
if ($status !== null) {
echo json_encode([
"status" => "success",
"data" => $status
]);
} else {
printFailure("Ride not found.");
}
+289
View File
@@ -0,0 +1,289 @@
<?php
/**
* passenger_socket.php
* =====================
* WebSocket Server للركاب — بورت 3030
* Internal HTTP Server — بورت 3031
*/
use Workerman\Worker;
use PHPSocketIO\SocketIO;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
require_once __DIR__ . '/vendor/autoload.php';
// ---------------------------------------------------------
// نظام تسجيل الأحداث (Logging System)
// ---------------------------------------------------------
$LOG_FILE = __DIR__ . '/socket_debug.log';
function socket_log($message, $data = null) {
global $LOG_FILE;
$date = date('Y-m-d H:i:s');
$logMsg = "[$date] $message";
if ($data !== null) {
$logMsg .= " | DATA: " . (is_string($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE));
}
$logMsg .= PHP_EOL;
echo $logMsg; // للطباعة في الكونسول إذا كان يعمل في الـ Foreground
@file_put_contents($LOG_FILE, $logMsg, FILE_APPEND); // الكتابة في الملف
}
// ---------------------------------------------------------
socket_log("=== STARTING PASSENGER SOCKET SERVER ===");
function loadEnvironment(string $filePath): void {
if (!file_exists($filePath)) {
socket_log("[WARNING] .env not found: $filePath");
return;
}
foreach (file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
if (str_starts_with(trim($line), '#') || !str_contains($line, '=')) continue;
[$name, $value] = explode('=', $line, 2);
putenv(trim($name) . '=' . trim($value, "\"'"));
}
}
loadEnvironment(dirname(__DIR__) . '/docker/.env');
loadEnvironment(dirname(__DIR__) . '/backend/.env');
loadEnvironment('/home/intaleq-rides/env/.env');
function getInternalSocketKey(): string {
$key = getenv('INTERNAL_SOCKET_KEY');
if ($key) return trim($key);
$path = getenv('INTERNAL_SOCKET_KEY_PATH');
if ($path && file_exists($path)) return trim((string) @file_get_contents($path));
if (file_exists('/keys/internal_socket_key')) return trim((string) @file_get_contents('/keys/internal_socket_key'));
if (file_exists('/home/location/.internal_socket_key')) return trim((string) @file_get_contents('/home/location/.internal_socket_key'));
if (file_exists('/home/intaleq-rides/.internal_socket_key')) return trim((string) @file_get_contents('/home/intaleq-rides/.internal_socket_key'));
return '';
}
$INTERNAL_KEY = getInternalSocketKey();
// ── Redis سيرفر الموقع (للتحقق من عضوية الراكب في خط مواصلاتي) ──
// نفس الـ Redis الذي يكتب عليه driver_socket و transit/functions.php.
// الهوست من REDIS_HOST حتى يعمل داخل Docker وخارجه على حدّ سواء.
$_locationRedis = null;
function getLocationRedis(): ?\Redis {
global $_locationRedis;
if ($_locationRedis !== null) {
try { $_locationRedis->ping(); return $_locationRedis; }
catch (\Exception $_) { $_locationRedis = null; }
}
try {
$redisPass = '';
foreach ([getenv('REDIS_PASS_KEY_PATH') ?: '', '/keys/.reds_pass_key', '/home/location/.reds_pass_key'] as $p) {
if (!empty($p) && file_exists($p)) { $redisPass = trim((string)@file_get_contents($p)); break; }
}
$host = getenv('REDIS_HOST') ?: (file_exists('/.dockerenv') ? 'redis' : '127.0.0.1');
$r = new \Redis();
$r->connect($host, (int)(getenv('REDIS_PORT') ?: 6379), 1.5);
if ($redisPass) $r->auth($redisPass);
$_locationRedis = $r;
return $r;
} catch (\Exception $e) {
socket_log("[REDIS_ERROR] Location Redis unavailable: " . $e->getMessage());
return null;
}
}
function getJwtSecret(): string {
$keyPath = getenv('JWT_SECRET_KEY_PATH');
if ($keyPath && file_exists($keyPath)) {
return trim(file_get_contents($keyPath));
}
if (file_exists('/keys/jwt_secret_key')) {
return trim(file_get_contents('/keys/jwt_secret_key'));
}
return getenv('JWT_SECRET_KEY') ?: (getenv('JWT_SECRET') ?: '');
}
if (empty($INTERNAL_KEY)) {
socket_log("[CRITICAL_ERROR] Internal key missing! Exiting.");
exit(1);
}
$PORT = 3030;
$INTERNAL_PORT = 3031;
$io = new SocketIO($PORT);
$io->on('workerStart', function () use ($io, $INTERNAL_KEY, $INTERNAL_PORT) {
$innerHttp = new Worker("http://0.0.0.0:$INTERNAL_PORT");
$innerHttp->onMessage = function ($connection, $request) use ($io, $INTERNAL_KEY) {
$headers = $request->header();
$clientIp = $connection->getRemoteIp();
if (($headers['x-internal-key'] ?? '') !== $INTERNAL_KEY) {
socket_log("[HTTP_ERROR] Unauthorized internal request from IP: $clientIp");
$connection->send('Unauthorized');
return;
}
$post = $request->post();
$action = trim($post['action'] ?? '');
if ($action === 'update_ride_status') {
$passengerId = $post['passenger_id'] ?? null;
$rawPayload = $post['payload'] ?? null;
if (!$passengerId || !$rawPayload) {
socket_log("[HTTP_ERROR] Missing passenger_id or payload for action: update_ride_status", $post);
$connection->send('Error: Missing passenger_id or payload');
return;
}
$payload = is_string($rawPayload)
? (json_decode($rawPayload, true) ?? $rawPayload)
: $rawPayload;
socket_log("[HTTP_SUCCESS] Emitting 'ride_status_change' to Passenger #$passengerId", $payload);
$io->to('passenger_' . $passengerId)->emit('ride_status_change', $payload);
$connection->send('OK');
} elseif ($action === 'update_driver_location') {
$passengerId = $post['passenger_id'] ?? null;
$rawPayload = $post['payload'] ?? null;
if (!$passengerId || !$rawPayload) {
socket_log("[HTTP_ERROR] Missing passenger_id or payload for action: update_driver_location", $post);
$connection->send('Error: Missing passenger_id or payload');
return;
}
$payload = is_string($rawPayload)
? (json_decode($rawPayload, true) ?? $rawPayload)
: $rawPayload;
socket_log("[HTTP_SUCCESS] Emitting 'driver_location_update' to Passenger #$passengerId", $payload);
$io->to('passenger_' . $passengerId)->emit('driver_location_update', $payload);
$connection->send('OK');
} elseif ($action === 'broadcast_bus_location') {
// 🚌 بثّ موقع الباص لكل ركاب الخط المشتركين (مواصلاتي)
// يصل من driver_socket بعد أن يبعث سائق الباص update_bus_location
$routeId = $post['route_id'] ?? null;
$rawPayload = $post['payload'] ?? null;
if (!$routeId || !$rawPayload) {
socket_log("[HTTP_ERROR] Missing route_id or payload for action: broadcast_bus_location", $post);
$connection->send('Error: Missing route_id or payload');
return;
}
$payload = is_string($rawPayload)
? (json_decode($rawPayload, true) ?? $rawPayload)
: $rawPayload;
socket_log("[HTTP_SUCCESS] Emitting 'bus_location_update' to route #$routeId", $payload);
$io->to('transit_route_' . $routeId)->emit('bus_location_update', $payload);
$connection->send('OK');
} else {
socket_log("[HTTP_WARNING] Unknown action received: $action", $post);
$connection->send('Unknown action: ' . $action);
}
};
$innerHttp->listen();
socket_log("[INFO] Internal HTTP started on port $INTERNAL_PORT");
});
$io->on('connection', function ($socket) {
$query = $socket->handshake['query'] ?? [];
$passengerId = $query['id'] ?? null;
$jwtToken = $query['jwt'] ?? ''; // JWT Token for authentication
$clientIp = $socket->conn->remoteAddress ?? 'Unknown';
if (!$passengerId || empty($jwtToken)) {
socket_log("[SOCKET_REJECTED] Connection rejected (No passenger ID or JWT missing) from IP: $clientIp");
$socket->disconnect();
return;
}
try {
$secretKey = getJwtSecret();
if (empty($secretKey)) {
socket_log("[WARNING] JWT Secret is not configured on the server!");
} else {
$decoded = JWT::decode($jwtToken, new Key($secretKey, 'HS256'));
if ((string)$decoded->sub !== (string)$passengerId || $decoded->role !== 'passenger') {
socket_log("[SOCKET_REJECTED] Connection rejected: Invalid JWT for passenger_id=$passengerId from IP: $clientIp");
$socket->disconnect();
return;
}
}
} catch (\Exception $e) {
socket_log("[SOCKET_REJECTED] Connection rejected: JWT Verification failed -> " . $e->getMessage() . " from IP: $clientIp");
$socket->disconnect();
return;
}
$socket->join('passenger_' . $passengerId);
socket_log("[SOCKET_CONNECTED] Passenger Connected: #$passengerId (IP: $clientIp)");
$socket->on('heartbeat', function ($data) {
// يمكن تفعيل السطر التالي للتأكد من النبضات إذا أردت دقة شديدة، لكنه قد يملأ ملف الـ log
// socket_log("[SOCKET_HEARTBEAT] Received from Passenger #$passengerId");
});
// 🚌 اشتراك الراكب في بثّ موقع باص خط (مواصلاتي)
// يُرفض إن لم يكن الراكب عضواً نشطاً في المؤسسة المالكة للخط
$socket->on('subscribe_transit_route', function ($data) use ($socket, $passengerId) {
$data = (array) $data;
$routeId = (int)($data['route_id'] ?? 0);
if ($routeId <= 0) return;
// transit:route_org:{routeId} → org_id (كُتب في route/approve.php)
// transit:org_members:{orgId} → SET من passenger_ids (transitCacheEnrollment)
$redis = getLocationRedis();
if ($redis) {
$orgId = $redis->get("transit:route_org:{$routeId}");
if ($orgId === false || $orgId === null) {
socket_log("[TRANSIT_BLOCKED] Route #{$routeId} not in Redis — passenger #{$passengerId}");
$socket->emit('transit_error', ['code' => 'ROUTE_NOT_FOUND', 'route_id' => $routeId]);
return;
}
if (!$redis->sIsMember("transit:org_members:{$orgId}", (string)$passengerId)) {
socket_log("[TRANSIT_BLOCKED] Passenger #{$passengerId} not enrolled in org #{$orgId} (route #{$routeId})");
$socket->emit('transit_error', ['code' => 'NOT_ENROLLED', 'route_id' => $routeId]);
return;
}
} else {
socket_log("[TRANSIT_WARN] Redis unavailable — enrollment check skipped (passenger #{$passengerId} route #{$routeId})");
}
$socket->join('transit_route_' . $routeId);
socket_log("[TRANSIT] Passenger #$passengerId subscribed to route #$routeId");
});
// 🚌 إلغاء الاشتراك عند إغلاق الراكب للخط
$socket->on('unsubscribe_transit_route', function ($data) use ($socket, $passengerId) {
$data = (array) $data;
$routeId = (int)($data['route_id'] ?? 0);
if ($routeId <= 0) return;
$socket->leave('transit_route_' . $routeId);
socket_log("[TRANSIT] Passenger #$passengerId unsubscribed from route #$routeId");
});
$socket->on('disconnect', function () use ($passengerId, $clientIp) {
socket_log("[SOCKET_DISCONNECTED] Passenger Disconnected: #$passengerId (IP: $clientIp)");
});
});
Worker::runAll();
File diff suppressed because it is too large Load Diff