<?php 
 
/* 
 * This file is part of Chevere. 
 * 
 * (c) Rodolfo Berrios <[email protected]> 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
declare(strict_types=1); 
 
foreach (['/', '/../../../'] as $path) { 
    $autoload = __DIR__ . $path . 'vendor/autoload.php'; 
    if (stream_resolve_include_path($autoload)) { 
        require $autoload; 
 
        break; 
    } 
} 
 
use function Chevere\Filesystem\dirForPath; 
use function Chevere\Filesystem\fileForPath; 
use Chevere\HrTime\HrTime; 
use Chevere\ThrowableHandler\ThrowableHandler; 
use function Chevere\Writer\streamFor; 
use Chevere\Writer\StreamWriter; 
use Chevere\Writer\Writers; 
use Chevere\Writer\WritersInstance; 
use Chevere\Xr\XrBuild; 
use Clue\React\Sse\BufferedChannel; 
use Psr\Http\Message\ServerRequestInterface; 
use React\EventLoop\Loop; 
use React\Http\HttpServer; 
use React\Http\Message\Response; 
use React\Http\Middleware\LimitConcurrentRequestsMiddleware; 
use React\Http\Middleware\RequestBodyBufferMiddleware; 
use React\Http\Middleware\RequestBodyParserMiddleware; 
use React\Http\Middleware\StreamingRequestMiddleware; 
use React\Stream\ThroughStream; 
use samejack\PHP\ArgvParser; 
 
include __DIR__ . '/meta.php'; 
 
new WritersInstance( 
    (new Writers()) 
        ->with( 
            new StreamWriter( 
                streamFor('php://output', 'w') 
            ) 
        ) 
        ->withError( 
            new StreamWriter( 
                streamFor('php://stderr', 'w') 
            ) 
        ) 
); 
set_error_handler(ThrowableHandler::ERROR_AS_EXCEPTION); 
register_shutdown_function(ThrowableHandler::SHUTDOWN_ERROR_AS_EXCEPTION); 
set_exception_handler(ThrowableHandler::CONSOLE); 
 
function writeToDebugger( 
    ServerRequestInterface $request, 
    BufferedChannel $channel, 
    string $action = 'message', 
): void { 
    $address = $request->getServerParams()['REMOTE_ADDR']; 
    $body = $request->getParsedBody() ?? []; 
    $message = $body['body'] ?? ''; 
    $message = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $message); 
    $emote = $body['emote'] ?? ''; 
    $topic = $body['topic'] ?? ''; 
    $id = $body['id'] ?? ''; 
    $file = $body['file_path'] ?? ''; 
    $line = $body['file_line'] ?? ''; 
    $fileDisplay = $file; 
    $fileDisplayShort = basename($file); 
    if ($line !== '') { 
        $fileDisplay .= ':' . $line; 
        $fileDisplayShort .= ':' . $line; 
    } 
    $channel->writeMessage( 
        json_encode([ 
            'message' => $message, 
            'file_path' => $file, 
            'file_line' => $line, 
            'file_display' => $fileDisplay, 
            'file_display_short' => $fileDisplayShort, 
            'emote' => $emote, 
            'topic' => $topic, 
            'id' => $id, 
            'action' => $action, 
        ]) 
    ); 
    echo "* [$address $action] $fileDisplay\n"; 
} 
 
echo "? Building "; 
echo strtr('v%v (%c)', [ 
    '%v' => XR_VERSION, 
    '%c' => XR_CODENAME, 
]) . "\n"; 
$timeStart = hrtime(true); 
 
try { 
    dirForPath(__DIR__ . '/locks')->removeContents(); 
} catch (Throwable) { 
} 
$build = new XrBuild( 
    dirForPath(__DIR__ . '/app/src'), 
    XR_VERSION, 
    XR_CODENAME 
); 
$app = fileForPath(__DIR__ . '/app/build/en.html'); 
$app->removeIfExists(); 
$app->create(); 
$app->put($build->html()); 
echo sprintf( 
    "* Done! [%s]\n", 
    (new HrTime(hrTime(true) - $timeStart)) 
        ->toReadMs() 
); 
$loop = Loop::get(); 
$channel = new BufferedChannel(); 
$handler = function (ServerRequestInterface $request) use ($channel, $loop) { 
    switch ($request->getUri()->getPath()) { 
        case '/': 
            return new Response( 
                '200', 
                ['Content-Type' => 'text/html'], 
                file_get_contents(__DIR__ . '/app/build/en.html') 
            ); 
        case '/locks': 
            $body = $request->getParsedBody() ?? []; 
            $lockFile = fileForPath(__DIR__ . '/locks/' . $body['id']); 
            $json = json_encode(['lock' => false]); 
            if ($lockFile->exists()) { 
                $json = $lockFile->getContents(); 
            } 
 
            return new Response( 
                '200', 
                ['Content-Type' => 'text/json'], 
                $json 
            ); 
        case '/lock-post': 
            $json = '{"lock":true}'; 
            $body = $request->getParsedBody() ?? []; 
            $lockFile = fileForPath(__DIR__ . '/locks/' . $body['id']); 
            $lockFile->removeIfExists(); 
            $lockFile->create(); 
            $lockFile->put($json); 
            writeToDebugger($request, $channel, 'pause'); 
 
            return new Response( 
                '200', 
                ['Content-Type' => 'text/json'], 
                $json 
            ); 
        case '/lock-patch': 
            $json = '{"stop":true}'; 
            $body = json_decode($request->getBody()->__toString(), true); 
            $lockFile = fileForPath(__DIR__ . '/locks/' . $body['id']); 
            $lockFile->removeIfExists(); 
            $lockFile->create(); 
            $lockFile->put($json); 
 
            return new Response( 
                '200', 
                ['Content-Type' => 'text/json'], 
                $json 
            ); 
        case '/lock-delete': 
            $body = json_decode($request->getBody()->__toString(), true); 
            $lockFile = fileForPath(__DIR__ . '/locks/' . $body['id']); 
            $lockFile->removeIfExists(); 
 
            return new Response( 
                '200', 
                ['Content-Type' => 'text/json'], 
                '{"ok":true}' 
            ); 
        case '/message': 
            if ($request->getMethod() !== 'POST') { 
                return new Response(405); 
            } 
            writeToDebugger($request, $channel); 
 
            return new Response( 
                '201', 
                ['Content-Type' => 'text/json'] 
            ); 
        case '/dump': 
            $stream = new ThroughStream(); 
            $id = $request->getHeaderLine('Last-Event-ID'); 
            $loop->futureTick(function () use ($channel, $stream, $id) { 
                $channel->connect($stream, $id); 
            }); 
            $serverParams = $request->getServerParams(); 
            $message = ['message' => 'New dump session started [' . $serverParams['REMOTE_ADDR'] . ']']; 
            $channel->writeMessage(json_encode($message)); 
            $stream->on('close', function () use ($stream, $channel, $request, $serverParams) { 
                $channel->disconnect($stream); 
                $message = ['message' => 'Dump session end [' . $serverParams['REMOTE_ADDR'] . ']']; 
                $channel->writeMessage(json_encode($message)); 
            }); 
 
            return new Response( 
                200, 
                ['Content-Type' => 'text/event-stream'], 
                $stream 
            ); 
        default: 
            return new Response(404); 
    } 
}; 
$http = new HttpServer( 
    $loop, 
    new StreamingRequestMiddleware(), 
    new LimitConcurrentRequestsMiddleware(100), 
    new RequestBodyBufferMiddleware(8 * 1024 * 1024), 
    new RequestBodyParserMiddleware(100 * 1024, 1), 
    $handler 
); 
$options = (new ArgvParser())->parseConfigs(); 
if (array_key_exists('h', $options) || array_key_exists('help', $options)) { 
    echo implode("\n", ['-p Port (default 27420)', '-c Cert .pem file', '']); 
    die(0); 
} 
$host = '0.0.0.0'; 
$port = $options['p'] ?? '0'; 
$cert = $options['c'] ?? null; 
$scheme = isset($cert) ? 'tls' : 'tcp'; 
$uri = "$scheme://$host:$port"; 
$context = $scheme === 'tcp' 
    ? [] 
    : [ 
        'tls' => [ 
            'local_cert' => $cert 
        ] 
    ]; 
$socket = new \React\Socket\SocketServer( 
    uri: $uri, 
    context: $context, 
    loop: $loop 
); 
$http->listen($socket); 
$socket->on('error', 'printf'); 
$scheme = parse_url($socket->getAddress(), PHP_URL_SCHEME); 
$httpAddress = strtr($socket->getAddress(), ['tls:' => 'https:', 'tcp:' => 'http:']); 
echo "XR Debug (ReactPHP SSE) listening on ($scheme) $httpAddress" . PHP_EOL; 
echo "---------------------------------------------------" . PHP_EOL; 
$loop->run(); 
 
 |