This commit is contained in:
Marcel Pociot
2020-04-16 15:30:53 +02:00
parent e49708b290
commit 2778d5a489
22 changed files with 960 additions and 206 deletions

View File

@@ -12,6 +12,7 @@ class Client
protected $loop;
protected $host;
protected $port;
public static $subdomains = [];
public function __construct(LoopInterface $loop, $host, $port)
{
@@ -31,6 +32,7 @@ class Client
$connection->authenticate($sharedUrl, $subdomain);
$clientConnection->on('authenticated', function ($data) {
static::$subdomains[] = "$data->subdomain.{$this->host}:{$this->port}";
dump("Connected to http://$data->subdomain.{$this->host}:{$this->port}");
});
});

View File

@@ -71,4 +71,11 @@ class Connection
],
]));
}
public function ping()
{
$this->socket->write(json_encode([
'event' => 'pong',
]));
}
}

View File

@@ -3,6 +3,8 @@
namespace App\Client;
use App\HttpServer\App;
use App\HttpServer\Controllers\AttachDataToLogController;
use App\HttpServer\Controllers\ClearLogsController;
use App\HttpServer\Controllers\DashboardController;
use App\HttpServer\Controllers\LogController;
use App\HttpServer\Controllers\ReplayLogController;
@@ -67,6 +69,8 @@ class Factory
$logRoute = new Route('/logs', ['_controller' => new LogController()], [], [], null, [], ['GET']);
$storeLogRoute = new Route('/logs', ['_controller' => new StoreLogController()], [], [], null, [], ['POST']);
$replayLogRoute = new Route('/replay/{log}', ['_controller' => new ReplayLogController()], [], [], null, [], ['GET']);
$attachLogDataRoute = new Route('/logs/{request_id}/data', ['_controller' => new AttachDataToLogController()], [], [], null, [], ['POST']);
$clearLogsRoute = new Route('/logs/clear', ['_controller' => new ClearLogsController()], [], [], null, [], ['GET']);
$this->app->route('/socket', new WsServer(new Socket()), ['*']);
@@ -74,19 +78,32 @@ class Factory
$this->app->routes->add('logs', $logRoute);
$this->app->routes->add('storeLogs', $storeLogRoute);
$this->app->routes->add('replayLog', $replayLogRoute);
$this->app->routes->add('attachLogData', $attachLogDataRoute);
$this->app->routes->add('clearLogs', $clearLogsRoute);
}
protected function detectNextFreeDashboardPort($port = 4040): int
{
while (is_resource(@fsockopen('127.0.0.1', $port))) {
$port++;
}
return $port;
}
public function createHttpServer()
{
$this->loop->futureTick(function () {
$dashboardUrl = 'http://127.0.0.1:4040/';
$dashboardPort = $this->detectNextFreeDashboardPort();
echo('Started Dashboard on port 4040'. PHP_EOL);
$this->loop->futureTick(function () use ($dashboardPort) {
$dashboardUrl = "http://127.0.0.1:{$dashboardPort}/";
echo('If the dashboard does not automatically open, visit: '.$dashboardUrl . PHP_EOL);
echo("Started Dashboard on port {$dashboardPort}" . PHP_EOL);
echo('If the dashboard does not automatically open, visit: ' . $dashboardUrl . PHP_EOL);
});
$this->app = new App('127.0.0.1', 4040, '0.0.0.0', $this->loop);
$this->app = new App('127.0.0.1', $dashboardPort, '0.0.0.0', $this->loop);
$this->addRoutes();

View File

@@ -5,6 +5,7 @@ namespace App\Commands;
use App\Server\Factory;
use Illuminate\Console\Scheduling\Schedule;
use LaravelZero\Framework\Commands\Command;
use React\EventLoop\LoopInterface;
class ServeCommand extends Command
{
@@ -15,6 +16,7 @@ class ServeCommand extends Command
public function handle()
{
(new Factory())
->setLoop(app(LoopInterface::class))
->setHost($this->argument('host'))
->setHostname($this->argument('hostname'))
->createServer()

View File

@@ -17,8 +17,8 @@ class ShareCommand extends Command
{
(new Factory())
->setLoop(app(LoopInterface::class))
//->setHost('beyond.sh')
//->setPort(8080)
->setHost('beyond.sh')
->setPort(8080)
->createClient($this->argument('host'), explode(',', $this->option('subdomain')))
->createHttpServer()
->run();

View File

@@ -0,0 +1,22 @@
<?php
namespace App\HttpServer\Controllers;
use Illuminate\Http\Request;
use App\Logger\RequestLogger;
class AttachDataToLogController extends PostController
{
public function handle(Request $request)
{
/** @var RequestLogger $requestLogger */
$requestLogger = app(RequestLogger::class);
$loggedRequest = $requestLogger->findLoggedRequest($request->get('request_id', ''));
if (! is_null($loggedRequest)) {
$loggedRequest->setAdditionalData((array)$request->get('data', []));
$requestLogger->pushLogs();
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\HttpServer\Controllers;
use App\Client\TunnelConnection;
use App\HttpServer\QueryParameters;
use App\Logger\RequestLogger;
use GuzzleHttp\Psr7\Response;
use Ratchet\ConnectionInterface;
use function GuzzleHttp\Psr7\str;
use Psr\Http\Message\RequestInterface;
class ClearLogsController extends Controller
{
public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
{
/** @var RequestLogger $logger */
$logger = app(RequestLogger::class);
$logger->clear();
$connection->send(
str(new Response(
200,
['Content-Type' => 'application/json'],
''
))
);
$connection->close();
}
}

View File

@@ -2,6 +2,7 @@
namespace App\HttpServer\Controllers;
use App\Client\Client;
use GuzzleHttp\Psr7\Response;
use function GuzzleHttp\Psr7\str;
use Psr\Http\Message\RequestInterface;
@@ -15,10 +16,19 @@ class DashboardController extends Controller
str(new Response(
200,
['Content-Type' => 'text/html'],
file_get_contents(base_path('resources/views/index.html'))
$this->getView()
))
);
$connection->close();
}
protected function getView(): string
{
$view = file_get_contents(base_path('resources/views/index.html'));
$view = str_replace('%subdomains%', implode(' ', Client::$subdomains), $view);
return $view;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\HttpServer\Controllers;
use App\HttpServer\QueryParameters;
use GuzzleHttp\Psr7\ServerRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Psr\Http\Message\RequestInterface;
use Ratchet\ConnectionInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
abstract class PostController extends Controller
{
public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
{
$connection->contentLength = $this->findContentLength($request->getHeaders());
$connection->requestBuffer = (string) $request->getBody();
$connection->request = $request;
$this->checkContentLength($connection);
}
public function onMessage(ConnectionInterface $from, $msg)
{
$from->requestBuffer .= $msg;
$this->checkContentLength($from);
}
protected function findContentLength(array $headers): int
{
return Collection::make($headers)->first(function ($values, $header) {
return strtolower($header) === 'content-length';
})[0] ?? 0;
}
protected function checkContentLength(ConnectionInterface $connection)
{
if (strlen($connection->requestBuffer) === $connection->contentLength) {
$laravelRequest = $this->createLaravelRequest($connection);
$this->handle($laravelRequest);
$connection->close();
unset($connection->requestBuffer);
unset($connection->contentLength);
unset($connection->request);
}
}
abstract public function handle(Request $request);
protected function createLaravelRequest(ConnectionInterface $connection): Request
{
$serverRequest = (new ServerRequest(
$connection->request->getMethod(),
$connection->request->getUri(),
$connection->request->getHeaders(),
$connection->requestBuffer,
$connection->request->getProtocolVersion()
))->withQueryParams(QueryParameters::create($connection->request)->all());
return Request::createFromBase((new HttpFoundationFactory)->createRequest($serverRequest));
}
}

View File

@@ -7,7 +7,9 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Laminas\Http\Request;
use Laminas\Http\Response;
use Namshi\Cuzzle\Formatter\CurlFormatter;
use Riverline\MultiPartParser\StreamedPart;
use function GuzzleHttp\Psr7\parse_request;
class LoggedRequest implements \JsonSerializable
{
@@ -35,12 +37,15 @@ class LoggedRequest implements \JsonSerializable
/** @var string */
protected $subdomain;
/** @var array */
protected $additionalData = [];
public function __construct(string $rawRequest, Request $parsedRequest)
{
$this->id = (string)Str::uuid();
$this->startTime = now();
$this->rawRequest = $rawRequest;
$this->parsedRequest = $parsedRequest;
$this->id = $this->getRequestId();
}
/**
@@ -61,6 +66,8 @@ class LoggedRequest implements \JsonSerializable
'body' => $this->isBinary($this->rawRequest) ? 'BINARY' : $this->parsedRequest->getContent(),
'query' => $this->parsedRequest->getQuery()->toArray(),
'post' => $this->getPost(),
'curl' => (new CurlFormatter())->format(parse_request($this->rawRequest)),
'additional_data' => $this->additionalData,
],
];
@@ -77,6 +84,11 @@ class LoggedRequest implements \JsonSerializable
return $data;
}
public function setAdditionalData(array $data)
{
$this->additionalData = array_merge($this->additionalData, $data);
}
protected function isBinary(string $string): bool
{
return preg_match('~[^\x20-\x7E\t\r\n]~', $string) > 0;
@@ -174,4 +186,9 @@ class LoggedRequest implements \JsonSerializable
{
return Arr::get($this->parsedRequest->getHeaders()->toArray(), 'X-Original-Host');
}
protected function getRequestId()
{
return Arr::get($this->parsedRequest->getHeaders()->toArray(), 'X-Expose-Request-ID', (string)Str::uuid());
}
}

View File

@@ -51,7 +51,14 @@ class RequestLogger
return $this->requests;
}
protected function pushLogs()
public function clear()
{
$this->requests = [];
$this->pushLogs();
}
public function pushLogs()
{
$this
->client

View File

@@ -23,6 +23,7 @@ class AppServiceProvider extends ServiceProvider
$this->app->singleton(RequestLogger::class, function () {
$browser = new Browser(app(LoopInterface::class));
return new RequestLogger($browser);
});
}

View File

@@ -32,14 +32,17 @@ class Connection
return array_pop($this->proxies);
}
public function rewriteHostInformation($serverHost, $port, string $data)
public function rewriteHostInformation($serverHost, $port, $requestId, string $data)
{
$appName = config('app.name');
$appVersion = config('app.version');
$originalHost = "{$this->subdomain}.{$serverHost}:{$port}";
$data = preg_replace('/Host: '.$this->subdomain.'.'.$serverHost.'(.*)\r\n/', "Host: {$this->host}\r\n" .
"X-Tunnel-By: {$appName} {$appVersion}\r\n" .
"X-Original-Host: {$this->subdomain}.{$serverHost}:{$port}\r\n", $data);
"X-Exposed-By: {$appName} {$appVersion}\r\n" .
"X-Expose-Request-ID: {$requestId}\r\n" .
"X-Original-Host: {$originalHost}\r\n", $data);
return $data;
}

View File

@@ -10,7 +10,7 @@ use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
use function GuzzleHttp\Psr7\parse_request;
class Shaft implements MessageComponentInterface
class Expose implements MessageComponentInterface
{
protected $connectionManager;

View File

@@ -60,7 +60,7 @@ class Factory
$connectionManager = new ConnectionManager($this->hostname, $this->port);
$app = new Shaft($connectionManager);
$app = new Expose($connectionManager);
return new IoServer($app, $socket, $this->loop);
}

View File

@@ -5,6 +5,7 @@ namespace App\Server\Messages;
use App\Server\Connections\ConnectionManager;
use Illuminate\Support\Str;
use Ratchet\ConnectionInterface;
use React\EventLoop\LoopInterface;
use stdClass;
class ControlMessage implements Message
@@ -45,6 +46,13 @@ class ControlMessage implements Message
'subdomain' => $connectionInfo->subdomain,
'client_id' => $connectionInfo->client_id
]));
$loop = app(LoopInterface::class);
$timer = $loop->addPeriodicTimer(5, function () use ($connection) {
$connection->send(json_encode([
'event' => 'ping'
]));
});
}
protected function registerProxy(ConnectionInterface $connection, $data)

View File

@@ -69,10 +69,10 @@ class TunnelMessage implements Message
private function copyDataToClient(Connection $clientConnection)
{
$data = $clientConnection->rewriteHostInformation($this->connectionManager->host(), $this->connectionManager->port(), $this->connection->buffer);
$requestId = uniqid();
$data = $clientConnection->rewriteHostInformation($this->connectionManager->host(), $this->connectionManager->port(), $requestId, $this->connection->buffer);
// Ask client to create a new proxy
$clientConnection->socket->send(json_encode([
'event' => 'createProxy',

View File

@@ -22,9 +22,13 @@
"cboden/ratchet": "^0.4.2",
"clue/buzz-react": "^2.7",
"guzzlehttp/guzzle": "^6.5",
"illuminate/http": "5.8.*|^6.0|^7.0",
"laminas/laminas-http": "^2.11",
"laravel-zero/framework": "^7.0",
"namshi/cuzzle": "^2.0",
"react/socket": "^1.4",
"symfony/http-kernel": "^4.0|^5.0",
"symfony/psr-http-message-bridge": "^1.1|^2.0",
"riverline/multipart-parser": "^2.0"
},
"require-dev": {
@@ -53,5 +57,5 @@
},
"minimum-stability": "dev",
"prefer-stable": true,
"bin": ["phunnel"]
"bin": ["expose"]
}

673
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5fb0e746e857756290dd83192601f8ec",
"content-hash": "f85ae104dc6c4806c3d1a158c5d182e6",
"packages": [
{
"name": "bfunky/http-parser",
@@ -659,7 +659,7 @@
},
{
"name": "illuminate/cache",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/cache.git",
@@ -710,7 +710,7 @@
},
{
"name": "illuminate/config",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/config.git",
@@ -754,7 +754,7 @@
},
{
"name": "illuminate/console",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/console.git",
@@ -808,16 +808,16 @@
},
{
"name": "illuminate/container",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/container.git",
"reference": "9108edffa95b34df94dc36721d92f380dd59bea3"
"reference": "10c5802e360595f5f2a8b6afa176b9542851e580"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/container/zipball/9108edffa95b34df94dc36721d92f380dd59bea3",
"reference": "9108edffa95b34df94dc36721d92f380dd59bea3",
"url": "https://api.github.com/repos/illuminate/container/zipball/10c5802e360595f5f2a8b6afa176b9542851e580",
"reference": "10c5802e360595f5f2a8b6afa176b9542851e580",
"shasum": ""
},
"require": {
@@ -848,20 +848,20 @@
],
"description": "The Illuminate Container package.",
"homepage": "https://laravel.com",
"time": "2020-03-18T13:25:12+00:00"
"time": "2020-04-06T13:33:36+00:00"
},
{
"name": "illuminate/contracts",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
"reference": "f13c89509345ceee05f3527a83f38c77c0f74579"
"reference": "69a40779d8fc43e3f43da973b9c0c20bdbd81203"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/contracts/zipball/f13c89509345ceee05f3527a83f38c77c0f74579",
"reference": "f13c89509345ceee05f3527a83f38c77c0f74579",
"url": "https://api.github.com/repos/illuminate/contracts/zipball/69a40779d8fc43e3f43da973b9c0c20bdbd81203",
"reference": "69a40779d8fc43e3f43da973b9c0c20bdbd81203",
"shasum": ""
},
"require": {
@@ -892,20 +892,20 @@
],
"description": "The Illuminate Contracts package.",
"homepage": "https://laravel.com",
"time": "2020-03-31T14:24:32+00:00"
"time": "2020-04-09T15:01:22+00:00"
},
{
"name": "illuminate/events",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/events.git",
"reference": "442fd9cdbacc0732eea4018bc1f0df7e4004b8e8"
"reference": "59f6074ff6b14b475c7c97021dcbcd2e2bce2ebd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/events/zipball/442fd9cdbacc0732eea4018bc1f0df7e4004b8e8",
"reference": "442fd9cdbacc0732eea4018bc1f0df7e4004b8e8",
"url": "https://api.github.com/repos/illuminate/events/zipball/59f6074ff6b14b475c7c97021dcbcd2e2bce2ebd",
"reference": "59f6074ff6b14b475c7c97021dcbcd2e2bce2ebd",
"shasum": ""
},
"require": {
@@ -937,11 +937,11 @@
],
"description": "The Illuminate Events package.",
"homepage": "https://laravel.com",
"time": "2020-02-11T22:47:28+00:00"
"time": "2020-04-14T13:14:16+00:00"
},
{
"name": "illuminate/filesystem",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/filesystem.git",
@@ -993,17 +993,120 @@
"time": "2020-03-26T19:53:03+00:00"
},
{
"name": "illuminate/support",
"version": "v7.4.0",
"name": "illuminate/http",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
"reference": "6b5a135637bf901e5dd9c0e3d906a97d550d4cee"
"url": "https://github.com/illuminate/http.git",
"reference": "c11e7175b7b751ce8ae5dfac7fbe46d47b6c2f39"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/support/zipball/6b5a135637bf901e5dd9c0e3d906a97d550d4cee",
"reference": "6b5a135637bf901e5dd9c0e3d906a97d550d4cee",
"url": "https://api.github.com/repos/illuminate/http/zipball/c11e7175b7b751ce8ae5dfac7fbe46d47b6c2f39",
"reference": "c11e7175b7b751ce8ae5dfac7fbe46d47b6c2f39",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/session": "^7.0",
"illuminate/support": "^7.0",
"php": "^7.2.5",
"symfony/http-foundation": "^5.0",
"symfony/http-kernel": "^5.0",
"symfony/mime": "^5.0"
},
"suggest": {
"ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
"guzzlehttp/guzzle": "Required to use the HTTP Client (^6.3.1|^7.0)."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "7.x-dev"
}
},
"autoload": {
"psr-4": {
"Illuminate\\Http\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "The Illuminate Http package.",
"homepage": "https://laravel.com",
"time": "2020-04-15T18:37:09+00:00"
},
{
"name": "illuminate/session",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/session.git",
"reference": "4285f5208e0a59286763c591281d5d6e66e66b84"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/session/zipball/4285f5208e0a59286763c591281d5d6e66e66b84",
"reference": "4285f5208e0a59286763c591281d5d6e66e66b84",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/contracts": "^7.0",
"illuminate/filesystem": "^7.0",
"illuminate/support": "^7.0",
"php": "^7.2.5",
"symfony/finder": "^5.0",
"symfony/http-foundation": "^5.0"
},
"suggest": {
"illuminate/console": "Required to use the session:table command (^7.0)."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "7.x-dev"
}
},
"autoload": {
"psr-4": {
"Illuminate\\Session\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "The Illuminate Session package.",
"homepage": "https://laravel.com",
"time": "2020-01-07T13:49:44+00:00"
},
{
"name": "illuminate/support",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
"reference": "b6f64a42377f86b293960e0b7f6920ae59e578d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/support/zipball/b6f64a42377f86b293960e0b7f6920ae59e578d2",
"reference": "b6f64a42377f86b293960e0b7f6920ae59e578d2",
"shasum": ""
},
"require": {
@@ -1052,20 +1155,20 @@
],
"description": "The Illuminate Support package.",
"homepage": "https://laravel.com",
"time": "2020-03-30T13:52:20+00:00"
"time": "2020-04-15T19:48:40+00:00"
},
{
"name": "illuminate/testing",
"version": "v7.4.0",
"version": "v7.6.2",
"source": {
"type": "git",
"url": "https://github.com/illuminate/testing.git",
"reference": "55fde302562febe3461c5732e6972b1626bfabcc"
"reference": "670d48ce1afd008d6e48b2a77c29691ffd4581a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/testing/zipball/55fde302562febe3461c5732e6972b1626bfabcc",
"reference": "55fde302562febe3461c5732e6972b1626bfabcc",
"url": "https://api.github.com/repos/illuminate/testing/zipball/670d48ce1afd008d6e48b2a77c29691ffd4581a3",
"reference": "670d48ce1afd008d6e48b2a77c29691ffd4581a3",
"shasum": ""
},
"require": {
@@ -1103,97 +1206,7 @@
],
"description": "The Illuminate Testing package.",
"homepage": "https://laravel.com",
"time": "2020-03-30T13:58:03+00:00"
},
{
"name": "jakub-onderka/php-console-color",
"version": "v0.2",
"source": {
"type": "git",
"url": "https://github.com/JakubOnderka/PHP-Console-Color.git",
"reference": "d5deaecff52a0d61ccb613bb3804088da0307191"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191",
"reference": "d5deaecff52a0d61ccb613bb3804088da0307191",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"jakub-onderka/php-code-style": "1.0",
"jakub-onderka/php-parallel-lint": "1.0",
"jakub-onderka/php-var-dump-check": "0.*",
"phpunit/phpunit": "~4.3",
"squizlabs/php_codesniffer": "1.*"
},
"type": "library",
"autoload": {
"psr-4": {
"JakubOnderka\\PhpConsoleColor\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Jakub Onderka",
"email": "jakub.onderka@gmail.com"
}
],
"abandoned": "php-parallel-lint/php-console-color",
"time": "2018-09-29T17:23:10+00:00"
},
{
"name": "jakub-onderka/php-console-highlighter",
"version": "v0.4",
"source": {
"type": "git",
"url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git",
"reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547",
"reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"jakub-onderka/php-console-color": "~0.2",
"php": ">=5.4.0"
},
"require-dev": {
"jakub-onderka/php-code-style": "~1.0",
"jakub-onderka/php-parallel-lint": "~1.0",
"jakub-onderka/php-var-dump-check": "~0.1",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~1.5"
},
"type": "library",
"autoload": {
"psr-4": {
"JakubOnderka\\PhpConsoleHighlighter\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jakub Onderka",
"email": "acci@acci.cz",
"homepage": "http://www.acci.cz/"
}
],
"description": "Highlight PHP code in terminal",
"abandoned": "php-parallel-lint/php-console-highlighter",
"time": "2018-09-29T18:48:56+00:00"
"time": "2020-04-14T14:05:27+00:00"
},
{
"name": "jolicode/jolinotif",
@@ -1642,16 +1655,16 @@
},
{
"name": "laravel-zero/foundation",
"version": "v7.4.0",
"version": "v7.6.1",
"source": {
"type": "git",
"url": "https://github.com/laravel-zero/foundation.git",
"reference": "99b7b78ceb78252249478e1cde228e5e48e3fd64"
"reference": "61438f6d9220d2e20f8610ccbe894507d89f8f1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-zero/foundation/zipball/99b7b78ceb78252249478e1cde228e5e48e3fd64",
"reference": "99b7b78ceb78252249478e1cde228e5e48e3fd64",
"url": "https://api.github.com/repos/laravel-zero/foundation/zipball/61438f6d9220d2e20f8610ccbe894507d89f8f1a",
"reference": "61438f6d9220d2e20f8610ccbe894507d89f8f1a",
"shasum": ""
},
"require": {
@@ -1680,7 +1693,7 @@
"framework",
"laravel"
],
"time": "2020-04-01T08:40:16+00:00"
"time": "2020-04-14T16:39:20+00:00"
},
{
"name": "laravel-zero/framework",
@@ -1852,6 +1865,56 @@
],
"time": "2020-03-17T18:58:12+00:00"
},
{
"name": "namshi/cuzzle",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/namshi/cuzzle.git",
"reference": "89849bb9c729a3d8aabf94c0b66e77c7df38abda"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/namshi/cuzzle/zipball/89849bb9c729a3d8aabf94c0b66e77c7df38abda",
"reference": "89849bb9c729a3d8aabf94c0b66e77c7df38abda",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0",
"php": ">=5.5.0",
"psr/log": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.2.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Namshi\\Cuzzle\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nikita Nefedov",
"email": "inefedor@gmail.com"
},
{
"name": "cirpo",
"email": "alessandro.cinelli@gmail.com"
}
],
"description": "Get the cURL shell command from a Guzzle request",
"time": "2016-11-23T08:01:36+00:00"
},
{
"name": "nesbot/carbon",
"version": "2.32.2",
@@ -1925,28 +1988,28 @@
},
{
"name": "nunomaduro/collision",
"version": "v4.1.3",
"version": "v4.2.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "a430bce33d1ad07f756ea6cae9afce9ef8670b42"
"reference": "d50490417eded97be300a92cd7df7badc37a9018"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/a430bce33d1ad07f756ea6cae9afce9ef8670b42",
"reference": "a430bce33d1ad07f756ea6cae9afce9ef8670b42",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/d50490417eded97be300a92cd7df7badc37a9018",
"reference": "d50490417eded97be300a92cd7df7badc37a9018",
"shasum": ""
},
"require": {
"facade/ignition-contracts": "^1.0",
"filp/whoops": "^2.4",
"jakub-onderka/php-console-highlighter": "^0.4",
"php": "^7.2.5",
"symfony/console": "^5.0"
},
"require-dev": {
"facade/ignition": "^2.0",
"fideloper/proxy": "^4.2",
"friendsofphp/php-cs-fixer": "^2.16",
"fruitcake/laravel-cors": "^1.0",
"laravel/framework": "^7.0",
"laravel/tinker": "^2.0",
@@ -1991,7 +2054,7 @@
"php",
"symfony"
],
"time": "2020-03-07T12:46:00+00:00"
"time": "2020-04-04T19:56:08+00:00"
},
{
"name": "nunomaduro/laravel-console-summary",
@@ -2321,6 +2384,52 @@
],
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": [
"events",
"psr",
"psr-14"
],
"time": "2019-01-08T18:20:26+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
@@ -3301,6 +3410,134 @@
"homepage": "https://symfony.com",
"time": "2020-03-30T14:14:32+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v5.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/24f40d95385774ed5c71dbf014edd047e2f2f3dc",
"reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"symfony/event-dispatcher-contracts": "^2"
},
"conflict": {
"symfony/dependency-injection": "<4.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/service-contracts": "^1.1|^2",
"symfony/stopwatch": "^4.4|^5.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2020-03-27T16:56:45+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "af23c2584d4577d54661c434446fb8fbed6025dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/af23c2584d4577d54661c434446fb8fbed6025dd",
"reference": "af23c2584d4577d54661c434446fb8fbed6025dd",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"psr/event-dispatcher": "^1"
},
"suggest": {
"symfony/event-dispatcher-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to dispatching event",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"time": "2019-11-18T17:27:11+00:00"
},
{
"name": "symfony/finder",
"version": "v5.0.7",
@@ -3405,6 +3642,102 @@
"homepage": "https://symfony.com",
"time": "2020-03-30T14:14:32+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v5.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "ad574c55d451127cab1c45b4ac51bf283e340cf0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/ad574c55d451127cab1c45b4ac51bf283e340cf0",
"reference": "ad574c55d451127cab1c45b4ac51bf283e340cf0",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"psr/log": "~1.0",
"symfony/error-handler": "^4.4|^5.0",
"symfony/event-dispatcher": "^5.0",
"symfony/http-foundation": "^4.4|^5.0",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-php73": "^1.9"
},
"conflict": {
"symfony/browser-kit": "<4.4",
"symfony/cache": "<5.0",
"symfony/config": "<5.0",
"symfony/dependency-injection": "<4.4",
"symfony/doctrine-bridge": "<5.0",
"symfony/form": "<5.0",
"symfony/http-client": "<5.0",
"symfony/mailer": "<5.0",
"symfony/messenger": "<5.0",
"symfony/translation": "<5.0",
"symfony/twig-bridge": "<5.0",
"symfony/validator": "<5.0",
"twig/twig": "<2.4"
},
"provide": {
"psr/log-implementation": "1.0"
},
"require-dev": {
"psr/cache": "~1.0",
"symfony/browser-kit": "^4.4|^5.0",
"symfony/config": "^5.0",
"symfony/console": "^4.4|^5.0",
"symfony/css-selector": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/dom-crawler": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/routing": "^4.4|^5.0",
"symfony/stopwatch": "^4.4|^5.0",
"symfony/translation": "^4.4|^5.0",
"symfony/translation-contracts": "^1.1|^2",
"twig/twig": "^2.4|^3.0"
},
"suggest": {
"symfony/browser-kit": "",
"symfony/config": "",
"symfony/console": "",
"symfony/dependency-injection": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\HttpKernel\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
"time": "2020-03-30T15:04:59+00:00"
},
{
"name": "symfony/mime",
"version": "v5.0.7",
@@ -3808,6 +4141,70 @@
"homepage": "https://symfony.com",
"time": "2020-03-27T16:56:45+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git",
"reference": "ce709cd9c90872c08c2427b45739d5f3c781ab4f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/ce709cd9c90872c08c2427b45739d5f3c781ab4f",
"reference": "ce709cd9c90872c08c2427b45739d5f3c781ab4f",
"shasum": ""
},
"require": {
"php": "^7.1",
"psr/http-message": "^1.0",
"symfony/http-foundation": "^4.4 || ^5.0"
},
"require-dev": {
"nyholm/psr7": "^1.1",
"symfony/phpunit-bridge": "^4.4 || ^5.0"
},
"suggest": {
"nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
},
"type": "symfony-bridge",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Bridge\\PsrHttpMessage\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "PSR HTTP message bridge",
"homepage": "http://symfony.com",
"keywords": [
"http",
"http-message",
"psr-17",
"psr-7"
],
"time": "2020-01-02T08:07:11+00:00"
},
{
"name": "symfony/routing",
"version": "v5.0.7",
@@ -4153,16 +4550,16 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v4.1.3",
"version": "v4.1.4",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "88f7acc95150bca002a498899f8b52f318e444c2"
"reference": "feb6dad5ae24b1380827aee1629b730080fde500"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/88f7acc95150bca002a498899f8b52f318e444c2",
"reference": "88f7acc95150bca002a498899f8b52f318e444c2",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/feb6dad5ae24b1380827aee1629b730080fde500",
"reference": "feb6dad5ae24b1380827aee1629b730080fde500",
"shasum": ""
},
"require": {
@@ -4213,7 +4610,7 @@
"env",
"environment"
],
"time": "2020-03-27T23:37:15+00:00"
"time": "2020-04-12T15:20:09+00:00"
},
{
"name": "voku/portable-ascii",

View File

@@ -13,7 +13,7 @@ return [
|
*/
'name' => 'Phunnel',
'name' => 'Expose',
/*
|--------------------------------------------------------------------------

View File

View File

@@ -2,9 +2,82 @@
<head>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tailwindcss/ui@latest/dist/tailwind-ui.min.css">
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?skin=sunburst"></script>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});
!function (a, b) {
"function" == typeof define && define.amd ? define([], b) : "undefined" != typeof module && module.exports ? module.exports = b() : a.ReconnectingWebSocket = b()
}(this, function () {
function a(b, c, d) {
function l(a, b) {
var c = document.createEvent("CustomEvent");
return c.initCustomEvent(a, !1, !1, b), c
}
var e = {
debug: !1,
automaticOpen: !0,
reconnectInterval: 1e3,
maxReconnectInterval: 3e4,
reconnectDecay: 1.5,
timeoutInterval: 2e3
};
d || (d = {});
for (var f in e) this[f] = "undefined" != typeof d[f] ? d[f] : e[f];
this.url = b, this.reconnectAttempts = 0, this.readyState = WebSocket.CONNECTING, this.protocol = null;
var h, g = this, i = !1, j = !1, k = document.createElement("div");
k.addEventListener("open", function (a) {
g.onopen(a)
}), k.addEventListener("close", function (a) {
g.onclose(a)
}), k.addEventListener("connecting", function (a) {
g.onconnecting(a)
}), k.addEventListener("message", function (a) {
g.onmessage(a)
}), k.addEventListener("error", function (a) {
g.onerror(a)
}), this.addEventListener = k.addEventListener.bind(k), this.removeEventListener = k.removeEventListener.bind(k), this.dispatchEvent = k.dispatchEvent.bind(k), this.open = function (b) {
h = new WebSocket(g.url, c || []), b || k.dispatchEvent(l("connecting")), (g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "attempt-connect", g.url);
var d = h, e = setTimeout(function () {
(g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "connection-timeout", g.url), j = !0, d.close(), j = !1
}, g.timeoutInterval);
h.onopen = function () {
clearTimeout(e), (g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onopen", g.url), g.protocol = h.protocol, g.readyState = WebSocket.OPEN, g.reconnectAttempts = 0;
var d = l("open");
d.isReconnect = b, b = !1, k.dispatchEvent(d)
}, h.onclose = function (c) {
if (clearTimeout(e), h = null, i) g.readyState = WebSocket.CLOSED, k.dispatchEvent(l("close")); else {
g.readyState = WebSocket.CONNECTING;
var d = l("connecting");
d.code = c.code, d.reason = c.reason, d.wasClean = c.wasClean, k.dispatchEvent(d), b || j || ((g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onclose", g.url), k.dispatchEvent(l("close")));
var e = g.reconnectInterval * Math.pow(g.reconnectDecay, g.reconnectAttempts);
setTimeout(function () {
g.reconnectAttempts++, g.open(!0)
}, e > g.maxReconnectInterval ? g.maxReconnectInterval : e)
}
}, h.onmessage = function (b) {
(g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onmessage", g.url, b.data);
var c = l("message");
c.data = b.data, k.dispatchEvent(c)
}, h.onerror = function (b) {
(g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onerror", g.url, b), k.dispatchEvent(l("error"))
}
}, 1 == this.automaticOpen && this.open(!1), this.send = function (b) {
if (h) return (g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "send", g.url, b), h.send(b);
throw"INVALID_STATE_ERR : Pausing to reconnect websocket"
}, this.close = function (a, b) {
"undefined" == typeof a && (a = 1e3), i = !0, h && h.close(a, b)
}, this.refresh = function () {
h && h.close()
}
}
return a.prototype.onopen = function () {
}, a.prototype.onclose = function () {
}, a.prototype.onconnecting = function () {
}, a.prototype.onmessage = function () {
}, a.prototype.onerror = function () {
}, a.debugAll = !1, a.CONNECTING = WebSocket.CONNECTING, a.OPEN = WebSocket.OPEN, a.CLOSING = WebSocket.CLOSING, a.CLOSED = WebSocket.CLOSED, a
});
</script>
<style>
.even\:bg-gray-50:nth-child(even) {
@@ -13,10 +86,29 @@
</style>
</head>
<body>
<div id="app" class="p-5 flex flex-row">
<div id="app" class="">
<div class="relative bg-indigo-600" style="marign-left: -1px">
<div class="max-w-screen-xl mx-auto py-3 px-3 sm:px-6 lg:px-8">
<div class="pr-16 sm:text-center sm:px-16">
<p class="font-medium text-white flex justify-center">
<span class="inline-block">Waiting for requests on: <a class="underline" target="_blank"
href="%subdomains%">%subdomains%</a></span>
</p>
</div>
</div>
</div>
<div class="p-5 flex flex-row">
<div class="w-1/3 flex flex-col mr-5">
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200">
<span class="inline-flex rounded-md shadow-sm mb-4">
<button @click.prevent="clearLogs"
type="button"
class="inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Clear
</button>
</span>
<div
class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200">
<table class="min-w-full">
<thead>
<tr>
@@ -32,30 +124,30 @@
</tr>
</thead>
<tbody class="bg-white">
<tr v-for="log in logs"
:class="{'bg-gray-100': currentLog === log}"
@click="setLog(log)">
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 font-medium text-gray-900">
<p>
{{ log.request.method }}
{{ log.request.uri }}
</p>
<span class="text-xs">{{ log.subdomain }}</span>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.response.status }} - {{ log.response.reason }}
</div>
<div v-else>
...
</div>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.duration }}ms
</div>
</td>
</tr>
<tr v-for="log in logs"
:class="{'bg-gray-100': currentLog === log}"
@click="setLog(log)">
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 font-medium text-gray-900">
<p>
{{ log.request.method }}
{{ log.request.uri }}
</p>
<span class="text-xs">{{ log.subdomain }}</span>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.response.status }} - {{ log.response.reason }}
</div>
<div v-else>
...
</div>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.duration }}ms
</div>
</td>
</tr>
</tbody>
</table>
</div>
@@ -68,11 +160,20 @@
{{ currentLog.request.method }} {{ currentLog.request.uri }}
<div class="flex-grow"></div>
<span class="inline-flex rounded-md shadow-sm">
<button @click.prevent="replay(currentLog)"
type="button" class="inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Replay
</button>
</span>
<button @click.prevent="replay(currentLog)"
type="button"
class="inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Replay
</button>
</span>
<span class="inline-flex rounded-md shadow-sm ml-4">
<button
:data-clipboard-text="currentLog.request.curl"
type="button"
class="clipboard inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Copy as curl
</button>
</span>
</h3>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
Status code: {{ currentLog.response?.status}}
@@ -135,7 +236,8 @@
{{ parameter.name }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<span v-if="parameter.is_file">File: {{ parameter.filename }} ({{ parameter.mime_type }})</span>
<span
v-if="parameter.is_file">File: {{ parameter.filename }} ({{ parameter.mime_type }})</span>
<span v-else>{{ parameter.value }}</span>
</dd>
</div>
@@ -177,14 +279,66 @@
{{ value }}
</dd>
</div>
<div v-if="currentLog.request.additional_data.length !== 0">
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-900">
Debug
</dt>
</div>
<div v-for="(value, key) in currentLog.request.additional_data"
:key="'debug'+key"
class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500">
{{ key }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<div v-html="value">
</dd>
</div>
</div>
<div>
<pre class="p-6 prettyprint">{{ currentLog.response.body }}</pre>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-900">
Response
</dt>
<div>
<div class="sm:hidden">
<select class="form-select block w-full">
<option>My Account</option>
<option>Company</option>
<option selected>Team Members</option>
<option>Billing</option>
</select>
</div>
<div class="hidden sm:block">
<nav class="flex">
<a href="#"
@click.prevent="setActiveTab('raw')"
:class="{'outline-none text-gray-700 bg-gray-100': activeTab === 'raw'}"
class="px-3 py-2 font-medium text-sm leading-5 rounded-md text-gray-500 hover:text-gray-700">
Raw
</a>
<a href="#"
@click.prevent="setActiveTab('preview')"
:class="{'outline-none text-gray-700 bg-gray-100': activeTab === 'preview'}"
class="ml-4 px-3 py-2 font-medium text-sm leading-5 rounded-md text-gray-500 hover:text-gray-700 focus:outline-none focus:text-gray-700 focus:bg-gray-100">
Preview
</a>
</nav>
</div>
</div>
</div>
<div v-if="activeTab === 'raw'">
<pre class="p-6 text-sm">{{ currentLog.response.body }}</pre>
</div>
<div v-if="activeTab === 'preview'">
<iframe :srcdoc="currentLog.response.body" style="height: 500px;" class="w-full h-full"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
new Vue({
@@ -193,35 +347,38 @@
data: {
currentLog: null,
view: 'request',
activeTab: 'raw',
logs: [],
},
methods: {
setLog: function(log) {
setActiveTab: function(tab) {
this.activeTab = tab;
},
clearLogs: function() {
fetch('/logs/clear');
},
setLog: function (log) {
this.currentLog = log;
this.$nextTick(function(){
PR.prettyPrint();
this.$nextTick(function () {
let clipboard = new ClipboardJS('.clipboard');
});
},
setView: function(view) {
setView: function (view) {
this.view = view;
this.$nextTick(function(){
PR.prettyPrint();
});
},
replay: function(log) {
fetch('/replay/'+log.id);
replay: function (log) {
fetch('/replay/' + log.id);
},
connect: function() {
connect: function () {
let conn = new ReconnectingWebSocket(`ws://${window.location.hostname}:${window.location.port}/socket`);
conn.onmessage = (e) => {
this.logs = JSON.parse(e.data);
};
},
loadLogs: function(log) {
loadLogs: function (log) {
fetch('/logs')
.then((response) => {
return response.json();
@@ -232,7 +389,7 @@
},
},
mounted: function() {
mounted: function () {
this.connect();
this.loadLogs();