start.php 5.17 KB
<?php
use Workerman\Worker;
use Workerman\Timer;

require_once __DIR__ . '/vendor/autoload.php';

// Define Heartbeat Interval
define('HEARTBEAT_TIME', 30);

// Create a WebSocket worker
$ws_worker = new Worker("websocket://0.0.0.0:8888");

// Emulate simple routing/state for now
// In production, use Redis for distributed state
$ws_worker->count = 1; // Single process for dev simplicity

// Redis Connection
$redis = null;
try {
    $redis_host = getenv('REDIS_HOST') ?: '127.0.0.1';
    $redis_port = getenv('REDIS_PORT') ?: 6379;
    $redis_auth = getenv('REDIS_PASSWORD');
    $redis_prefix = getenv('REDIS_PREFIX') ?: 'ai_';

    $params = [
        'scheme' => 'tcp',
        'host' => $redis_host,
        'port' => $redis_port,
    ];

    if ($redis_auth) {
        $params['password'] = $redis_auth;
    }

    // Predis Client
    $redis = new Predis\Client($params, ['prefix' => $redis_prefix]);
    $redis->connect();
    echo "✅ Connected to Redis at $redis_host ($redis_prefix)\n";
} catch (Exception $e) {
    echo "⚠️ Redis Connection Failed: " . $e->getMessage() . "\n";
}

// Store connections (Memory for now, can move to Redis later)
$clients = []; // ClientID -> Connection
$devices = []; // DeviceID -> Connection

$ws_worker->onWorkerStart = function ($worker) {
    echo "Relay Server Started on 0.0.0.0:8888\n";

    // Heartbeat check
    Timer::add(10, function () use ($worker) {
        $time_now = time();
        foreach ($worker->connections as $connection) {
            // Check if connection is alive possibly? 
            // Workerman handles basic disconnects, but we can enforce ping logic here if needed
        }
    });
};

$ws_worker->onConnect = function ($connection) {
    echo "New connection: " . $connection->id . "\n";
    $connection->authVerified = false;
};

$ws_worker->onMessage = function ($connection, $data) use (&$clients, &$devices) {
    $msg = json_decode($data, true);
    if (!$msg || !isset($msg['type'])) {
        return;
    }

    // 1. Authenticate / Register
    if ($msg['type'] === 'register') {
        if ($msg['role'] === 'device') {
            // Device 注册
            $deviceId = $msg['id']; // TODO: Add secret validation
            $devices[$deviceId] = $connection;
            $connection->deviceId = $deviceId;
            $connection->role = 'device';
            $connection->authVerified = true;
            $connection->send(json_encode(['type' => 'ack', 'status' => 'registered']));
            echo "Device Registered: $deviceId\n";
        } elseif ($msg['role'] === 'client') {
            // Mini Program 注册
            // TODO: Validate Token
            $clientId = $msg['id'];
            $clients[$clientId] = $connection;
            $connection->clientId = $clientId;
            $connection->role = 'client';
            $connection->authVerified = true;
            $connection->send(json_encode(['type' => 'ack', 'status' => 'connected']));
            echo "Client Connected: $clientId\n";
        }
        return;
    }

    if (!$connection->authVerified) {
        $connection->close();
        return;
    }

    // 2. Proxy Logic

    // Client -> Device
    if ($msg['type'] === 'proxy' && $connection->role === 'client') {
        $targetDeviceId = $msg['targetDeviceId'] ?? null;
        if ($targetDeviceId && isset($devices[$targetDeviceId])) {
            $payload = $msg['payload'];
            // Wrap it so device knows who sent it
            $forwardMsg = [
                'type' => 'cmd:execute',
                'fromClientId' => $connection->clientId,
                'payload' => $payload
            ];
            $devices[$targetDeviceId]->send(json_encode($forwardMsg));
            echo "Forwarded msg from Client {$connection->clientId} to Device {$targetDeviceId}\n";
        } else {
            $connection->send(json_encode(['type' => 'error', 'msg' => 'Device offline or not found']));
        }
    }

    // Device -> Client
    if ($msg['type'] === 'proxy_response' && $connection->role === 'device') {
        $targetClientId = $msg['targetClientId'] ?? null;
        if ($targetClientId && isset($clients[$targetClientId])) {
            $payload = $msg['payload'];
            $forwardMsg = [
                'type' => 'response',
                'fromDeviceId' => $connection->deviceId,
                'payload' => $payload
            ];
            $clients[$targetClientId]->send(json_encode($forwardMsg));
            echo "Forwarded response from Device {$connection->deviceId} to Client {$targetClientId}\n";
        }
    }
};

$ws_worker->onClose = function ($connection) use (&$clients, &$devices) {
    if (isset($connection->role)) {
        if ($connection->role === 'device' && isset($connection->deviceId)) {
            unset($devices[$connection->deviceId]);
            echo "Device disconnected: {$connection->deviceId}\n";
        } elseif ($connection->role === 'client' && isset($connection->clientId)) {
            unset($clients[$connection->clientId]);
            echo "Client disconnected: {$connection->clientId}\n";
        }
    }
};

Worker::runAll();