Accedi
IT
apps/jumphp-ide-remote / src / Main.php · commit b81e652
<?phpnamespace App;use JumPHP\Kernel\Platform;use JumPHP\Net\RuntimeLink;use JumPHP\Net\Tcp;use JumPHP\Net\TcpConnection;use JumPHP\Runtime\RuntimeClient;use JumPHP\Tauri\App;use JumPHP\Tauri\AppMain;use JumPHP\Tauri\BrowserRuntime;/** * IDE Remote, PHP side. The pages do the crypto and the protocol (ui/js/channel.js); PHP only *  - gives the page its settings and texts (`settings`, `strings`), *  - is the page's TCP socket for the DIRECT mode (`direct.open|write|close`, events `direct`): through *    JumPHP\Net\Tcp, so it is a plain socket on the desktop and the JumPHP runtime in a browser (rule 4 of AGENTS.md), *  - tells the page what the JumPHP runtime says (`runtime.state`, `runtime.pair`). * Bytes cross as base64 and stay ciphertext: PHP never sees a key. */final class Main implements AppMain{    public const RELAY_DEFAULT = 'wss://jumphp.net/ide-relay/ws';    /** @var array<string, TcpConnection> */    private array $connections = [];    private ?RuntimeClient $client = null;    public function boot(App $app): void    {        $tcp = new Tcp($app->config, $app->http(), fn (): RuntimeLink => $this->runtimeLink($app));        $app->gameLoop->addPeriodicTimer(0.02, fn () => $tcp->poll());        $app->command('settings', fn (mixed $p): array => $this->settings($app));        $app->command('strings', fn (mixed $p): array => $app->config->file('lang.yaml'));        $app->command('direct.open', function (mixed $p) use ($app, $tcp): array {            $p = is_array($p) ? $p : [];            $id = (string)($p['id'] ?? '');            $host = (string)($p['host'] ?? '');            $port = (int)($p['port'] ?? 0);            if ($id === '' || !self::validTarget($host, $port)) {                return ['ok' => false, 'error' => 'bad target'];            }            $c = $tcp->connect($host, $port, 3.0);            $this->connections[$id] = $c;            $send = fn (string $type, array $more = []) => $app->send('direct', ['id' => $id, 'type' => $type] + $more);            $c->on('open', fn () => $send('open'));            $c->on('data', fn (string $d) => $send('data', ['data' => base64_encode($d)]));            $c->on('error', fn (string $m) => $send('error', ['message' => $m]));            $c->on('close', function (?string $why) use ($id, $send): void {                unset($this->connections[$id]);                $send('close', ['message' => (string)$why]);            });            return ['ok' => true];        });        $app->command('direct.write', function (mixed $p): bool {            $c = $this->connections[(string)($p['id'] ?? '')] ?? null;            $bytes = base64_decode((string)($p['data'] ?? ''), true);            if ($c === null || $bytes === false) {                return false;            }            $c->write($bytes);            return true;        });        $app->command('direct.close', function (mixed $p): bool {            $c = $this->connections[(string)($p['id'] ?? '')] ?? null;            $c?->close();            return $c !== null;        });        $app->command('runtime.state', fn (mixed $p): array => ['state' => $this->runtimeLink($app)->state()]);        $app->command('runtime.pair', function (mixed $p) use ($app) {            $client = $this->runtimeLink($app);            return $client instanceof RuntimeClient ? $client->pair($app->config->id(), $app->config->title()) : false;        });    }    /** @return array{relay_url:string, net_mode:string, native:bool, version:string, direct_head_start:float} */    public function settings(App $app): array    {        $relay = (string)$app->config->get('ide_relay_url', self::RELAY_DEFAULT);        return [            'relay_url' => $relay !== '' ? $relay : self::RELAY_DEFAULT,            'net_mode' => $app->config->netMode(),            'native' => !Platform::isWasm(),            'version' => (string)$app->config->get('version', ''),            'direct_head_start' => 1.5,        ];    }    /** A LAN or IPv6 literal / name and a real port: nothing else may be dialled from here. */    public static function validTarget(string $host, int $port): bool    {        return $port >= 1 && $port <= 65535 && preg_match('/^[A-Za-z0-9._:\-]{1,253}$/', $host) === 1;    }    /** In the browser: the client of the JumPHP runtime (looks for it by itself); elsewhere NoRuntime-like behaviour (plain sockets). */    private function runtimeLink(App $app): RuntimeLink    {        if ($this->client === null && $app->runtime() instanceof BrowserRuntime && Platform::isWasm()) {            $this->client = new RuntimeClient($app->runtime()->bridge, $app->config);            $client = $this->client;            $app->gameLoop->addPeriodicTimer(0.05, fn () => $client->update(0.05));            $client->autoConnect();        }        return $this->client ?? new \JumPHP\Net\NoRuntime();    }}