├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── php.yml
├── .gitignore
├── routes
└── larapoke.php
├── tests
├── RegistersPackages.php
├── ScaffoldAuth.php
└── Unit
│ ├── Script
│ ├── ScriptTest.php
│ └── ScriptRouteTest.php
│ ├── LarapokeServiceProviderTest.php
│ ├── Routes
│ └── RouteGeneratorTest.php
│ └── Modes
│ ├── ModeBladeTest.php
│ ├── ModeAutoTest.php
│ └── ModeMiddlewareTest.php
├── src
├── Http
│ ├── Controllers
│ │ └── LarapokeController.php
│ ├── Middleware
│ │ ├── LarapokeGlobalMiddleware.php
│ │ ├── LarapokeMiddleware.php
│ │ └── InjectsLarapokeScript.php
│ └── RouteGenerator.php
├── Blade
│ └── LarapokeDirective.php
└── LarapokeServiceProvider.php
├── phpunit.xml
├── LICENSE
├── resources
└── views
│ └── script.blade.php
├── composer.json
├── config
└── larapoke.php
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # Help me support this package
2 |
3 | ko_fi: DarkGhostHunter
4 | custom: ['https://paypal.me/darkghosthunter']
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /tests/Browser/console
3 | /tests/Browser/screenshots
4 | composer.phar
5 | composer.lock
6 | .idea
7 | /build/
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "09:00"
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/routes/larapoke.php:
--------------------------------------------------------------------------------
1 | setRoutes();
--------------------------------------------------------------------------------
/tests/RegistersPackages.php:
--------------------------------------------------------------------------------
1 | isInjectable($request, $response)) {
24 | $this->injectScript($response);
25 | }
26 |
27 | return $response;
28 | }
29 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
')) { 75 | return $response; 76 | } 77 | 78 | return $response->setContent( 79 | substr_replace($content, app(LarapokeDirective::class)->toHtml(), $endBodyPosition, 0) 80 | ); 81 | } 82 | } -------------------------------------------------------------------------------- /src/Http/RouteGenerator.php: -------------------------------------------------------------------------------- 1 | config = $config; 35 | $this->router = $router; 36 | } 37 | 38 | /** 39 | * Parses the configuration from Larapoke 40 | * 41 | * @return array 42 | */ 43 | protected function parseConfig(): array 44 | { 45 | $configs = array_flip([ 46 | 'route', 47 | 'name', 48 | 'domain', 49 | 'middleware', 50 | ]); 51 | 52 | foreach ($configs as $key => &$config) { 53 | $config = $this->config->get('larapoke.poking.'.$key); 54 | } 55 | 56 | return $configs; 57 | } 58 | 59 | /** 60 | * Automatically registers routes 61 | * 62 | * @return void 63 | */ 64 | public function setRoutes() 65 | { 66 | $config = $this->parseConfig(); 67 | 68 | // When the "domain" config is null, we will just register a global route 69 | // that will respond to all domains. Otherwise, we will wrap the value 70 | // and traverse the array to register each to its own domain name. 71 | if ($config['domain'] === null) { 72 | $this->route($config)->name($config['name']); 73 | return; 74 | } 75 | 76 | // If its just one domain, we will register it and then exit 77 | if (is_string($config['domain'])) { 78 | $this->route($config)->name($config['domain'].'.'.$config['name'])->domain($config['domain']); 79 | return; 80 | } 81 | 82 | foreach (Arr::wrap($config['domain']) as $domain) { 83 | $this->route($config)->name($domain.'.'.$config['name'])->domain($domain); 84 | } 85 | } 86 | 87 | /** 88 | * Returns a Larapoke route 89 | * 90 | * @param array $config 91 | * @return \Illuminate\Routing\Route 92 | */ 93 | protected function route(array $config) 94 | { 95 | $route = $this->router 96 | ->match('head', $config['route']) 97 | ->uses(LarapokeController::class); 98 | 99 | $route->middleware($config['middleware']); 100 | 101 | return $route; 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /src/LarapokeServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__ . '/../config/larapoke.php', 'larapoke'); 24 | 25 | $this->app->singleton(LarapokeDirective::class, function ($app) { 26 | return new LarapokeDirective($app['config'], $app['view'], $app['url']); 27 | }); 28 | } 29 | 30 | /** 31 | * Bootstrap any application services. 32 | * 33 | * @param \Illuminate\Routing\Router $router 34 | * @param \Illuminate\Contracts\Config\Repository $config 35 | * @param \Illuminate\View\Compilers\BladeCompiler $blade 36 | * @return void 37 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 38 | */ 39 | public function boot(Router $router, Repository $config, BladeCompiler $blade): void 40 | { 41 | $this->loadRoutesFrom(__DIR__ . '/../routes/larapoke.php'); 42 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'larapoke'); 43 | 44 | $this->bootMiddleware($router, $config); 45 | 46 | $this->bootBladeDirective($blade); 47 | 48 | if ($this->app->runningInConsole()) { 49 | $this->publishes([__DIR__ . '/../config/larapoke.php' => config_path('larapoke.php')], 'config'); 50 | $this->publishes([__DIR__ . '/../resources/views' => resource_path('views/vendor/larapoke')], 'views'); 51 | } 52 | } 53 | 54 | /** 55 | * Registers (or push globally) the Middleware 56 | * 57 | * @param \Illuminate\Routing\Router $router 58 | * @param \Illuminate\Contracts\Config\Repository $config 59 | * @return void 60 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 61 | */ 62 | protected function bootMiddleware(Router $router, Repository $config): void 63 | { 64 | $router->aliasMiddleware('larapoke', LarapokeMiddleware::class); 65 | 66 | // If Larapoke is set to auto, push the global middleware. 67 | if ($config->get('larapoke.mode') === 'auto') { 68 | $this->app->make(Kernel::class)->pushMiddleware(LarapokeGlobalMiddleware::class); 69 | } 70 | } 71 | 72 | /** 73 | * Registers the Blade Directive 74 | * 75 | * @param \Illuminate\View\Compilers\BladeCompiler $blade 76 | * @return void 77 | */ 78 | protected function bootBladeDirective(BladeCompiler $blade): void 79 | { 80 | $blade->directive('larapoke', function () { 81 | return $this->app->make(LarapokeDirective::class)->toHtml(); 82 | }); 83 | } 84 | } -------------------------------------------------------------------------------- /tests/Unit/Script/ScriptTest.php: -------------------------------------------------------------------------------- 1 | mockConfig = \Mockery::mock(\Illuminate\Config\Repository::class); 29 | 30 | $this->mockView = \Mockery::mock(\Illuminate\View\Factory::class); 31 | 32 | $this->mockUrl = \Mockery::spy(\Illuminate\Contracts\Routing\UrlGenerator::class); 33 | } 34 | 35 | public function testReceivesConfig() 36 | { 37 | $this->mockView 38 | ->shouldReceive('make') 39 | ->with('custom-larapoke-view', \Mockery::type('array')) 40 | ->andReturnUsing(function ($script, $config) { 41 | return new class ($config) 42 | { 43 | protected $config; 44 | 45 | public function __construct($config) 46 | { 47 | $this->config = $config; 48 | } 49 | 50 | public function render() 51 | { 52 | return json_encode($this->config); 53 | } 54 | }; 55 | }); 56 | 57 | $this->mockConfig->shouldReceive('get') 58 | ->with('session.lifetime') 59 | ->andReturn($this->sessionLifetime = rand(10, 240)); 60 | 61 | $this->mockConfig->shouldReceive('get') 62 | ->with('larapoke.poking.route') 63 | ->andReturn($route = 'test-larapoke-route'); 64 | 65 | $this->mockConfig->shouldReceive('get') 66 | ->with('larapoke.times') 67 | ->andReturn($this->times = rand(2, 16)); 68 | 69 | $this->mockConfig->shouldReceive('get') 70 | ->with('larapoke.view') 71 | ->andReturn('custom-larapoke-view'); 72 | 73 | 74 | $this->mockUrl->shouldReceive('to') 75 | ->once() 76 | ->with($route) 77 | ->andReturn('http://test-app.com/'.$route); 78 | 79 | $script = (new LarapokeDirective( 80 | $this->mockConfig, $this->mockView, $this->mockUrl) 81 | )->toHtml(); 82 | 83 | $this->assertEquals( 84 | 'http://test-app.com/test-larapoke-route', 85 | json_decode($script, true)['route'] 86 | ); 87 | $this->assertEquals( 88 | (int)((($this->sessionLifetime * 60 * 1000) / $this->times)), 89 | json_decode($script, true)['interval'] 90 | ); 91 | $this->assertEquals( 92 | $this->sessionLifetime * 60 * 1000, 93 | json_decode($script, true)['lifetime'] 94 | ); 95 | } 96 | } -------------------------------------------------------------------------------- /tests/Unit/LarapokeServiceProviderTest.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'view', 'config' 19 | ] 20 | ]; 21 | 22 | protected function getEnvironmentSetUp($app) 23 | { 24 | $router = $app->make('router'); 25 | 26 | $router->group(['web'], function() use ($router) { 27 | $router->get('/test', function () { 28 | return 'ok'; 29 | }); 30 | }); 31 | } 32 | 33 | public function testReceivesDefaultConfig() 34 | { 35 | $this->assertEquals( 36 | include __DIR__ . '/../../config/larapoke.php', 37 | $this->app['config']['larapoke'] 38 | ); 39 | } 40 | 41 | public function testPublishesConfigFile() 42 | { 43 | $this->artisan('vendor:publish', [ 44 | '--provider' => 'DarkGhostHunter\Larapoke\LarapokeServiceProvider' 45 | ]); 46 | 47 | $this->assertFileExists(config_path('larapoke.php')); 48 | $this->assertFileIsReadable(config_path('larapoke.php')); 49 | $this->assertFileEquals(config_path('larapoke.php'), __DIR__ . '/../../config/larapoke.php'); 50 | $this->assertTrue(unlink(config_path('larapoke.php'))); 51 | } 52 | 53 | public function testLoadDefaultRoute() 54 | { 55 | /** @var \Illuminate\Routing\Router $router */ 56 | $router = $this->app->make('router'); 57 | 58 | /** @var \Illuminate\Routing\Route $route */ 59 | $route = $router->getRoutes()->match( 60 | $this->app->make('request')->create('/poke', 'HEAD') 61 | ); 62 | 63 | $this->assertEquals('larapoke', $route->getName()); 64 | $this->assertInstanceOf(LarapokeController::class, $route->getController()); 65 | } 66 | 67 | public function testLoadDefaultView() 68 | { 69 | $script = $this->app->make('view') 70 | ->make('larapoke::script') 71 | ->with([ 72 | 'route' => '/poke', 73 | 'interval' => 100, 74 | 'timeout' => true, 75 | 'lifetime' => 400000, 76 | ]) 77 | ->render(); 78 | 79 | $this->assertIsString($script); 80 | $this->assertStringContainsString('larapoke_', $script); 81 | } 82 | 83 | public function testRegistersGlobalMiddleware() 84 | { 85 | /** @var \Illuminate\Routing\Router $router */ 86 | $router = $this->app->make('router'); 87 | 88 | $this->assertTrue($this->app->make(Kernel::class)->hasMiddleware(LarapokeGlobalMiddleware::class)); 89 | } 90 | 91 | public function testRegistersMiddlewareAlias() 92 | { 93 | /** @var \Illuminate\Routing\Router $router */ 94 | $router = $this->app->make('router'); 95 | 96 | $this->assertArrayHasKey('larapoke', $router->getMiddleware()); 97 | } 98 | 99 | public function testRegistersBladeDirective() 100 | { 101 | /** @var \Illuminate\View\Factory $view */ 102 | $view = $this->app->make('view'); 103 | 104 | $directives = $view->getEngineResolver() 105 | ->resolve('blade') 106 | ->getCompiler() 107 | ->getCustomDirectives(); 108 | 109 | $this->assertArrayHasKey('larapoke', $directives); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/Unit/Script/ScriptRouteTest.php: -------------------------------------------------------------------------------- 1 | set('session.lifetime', 90); 20 | 21 | $app['config']->set('larapoke', [ 22 | 'mode' => 'auto', 23 | 'times' => 8, 24 | 'timeout' => false, 25 | 'poking' => [ 26 | 'route' => 'test-larapoke-route', 27 | 'name' => 'test-larapoke-name', 28 | 'domain' => 'test-subdomain.app.com', 29 | 'middleware' => ['web', 'testgroup'], 30 | ] 31 | ]); 32 | } 33 | 34 | protected function getEnvironmentSetUp($app) 35 | { 36 | $this->scaffoldAuth($app); 37 | 38 | $app->bind('testgroup', function() { 39 | return new class() { 40 | public function handle($request, $next) 41 | { 42 | return $next($request); 43 | } 44 | }; 45 | }); 46 | } 47 | 48 | protected function tearDown() : void 49 | { 50 | parent::tearDown(); 51 | 52 | $this->cleanScaffold(); 53 | } 54 | 55 | protected function setUp() : void 56 | { 57 | parent::setUp(); 58 | 59 | /** @var \Illuminate\Routing\Router $router */ 60 | $router = $this->app->make('router'); 61 | 62 | $router->group(['middleware' => ['web']], function () use ($router) { 63 | $router->get('/register', function () { 64 | return $this->app->make(Factory::class)->make('auth.register'); 65 | })->name('register'); 66 | $router->get('/login', function () { 67 | return $this->app->make(Factory::class)->make('auth.login'); 68 | })->name('login'); 69 | $router->get('/home', function () { 70 | return $this->app->make(Factory::class)->make('home'); 71 | })->name('home'); 72 | }); 73 | } 74 | 75 | public function testPokeExpired() 76 | { 77 | $content = $this->get('/register')->content(); 78 | 79 | $matches = []; 80 | 81 | preg_match( 82 | '//', 83 | $content, 84 | $matches 85 | ); 86 | 87 | $csrfToken = $matches[1]; 88 | 89 | $this->app->make('session')->flush(); 90 | 91 | $response = $this->get('/test-larapoke-route', [ 92 | '_token' => $csrfToken, 93 | ]); 94 | 95 | $response->assertStatus(404); 96 | } 97 | 98 | public function testDifferentRouteAndSubdomain() 99 | { 100 | $request = $this->call( 101 | 'HEAD', 102 | 'http://test-subdomain.app.com/test-larapoke-route', [], [], [], 103 | $this->transformHeadersToServerVars([]) 104 | ); 105 | 106 | $request->assertStatus(204); 107 | $this->assertEmpty($request->content()); 108 | } 109 | 110 | public function testWrongMethodGives405() 111 | { 112 | foreach (['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as $method) { 113 | 114 | $request = $this->call( 115 | $method, 116 | 'http://test-subdomain.app.com/test-larapoke-route', [], [], [], 117 | $this->transformHeadersToServerVars([]) 118 | ); 119 | $request->assertStatus(405); 120 | } 121 | } 122 | 123 | public function testHasNamedRoute() 124 | { 125 | $this->assertTrue( 126 | $this->app->make('router')->getRoutes()->hasNamedRoute('test-subdomain.app.com.test-larapoke-name') 127 | ); 128 | } 129 | 130 | public function testHasMiddlewareGroup() 131 | { 132 | /** @var \Illuminate\Routing\Router $router */ 133 | $router = $this->app->make('router'); 134 | 135 | $route = $router->getRoutes()->getByName('test-subdomain.app.com.test-larapoke-name'); 136 | 137 | $this->assertTrue(in_array('testgroup', $route->getAction('middleware'))); 138 | $this->assertTrue(in_array('web', $route->getAction('middleware'))); 139 | } 140 | } -------------------------------------------------------------------------------- /tests/Unit/Routes/RouteGeneratorTest.php: -------------------------------------------------------------------------------- 1 | config = \Mockery::spy(\Illuminate\Contracts\Config\Repository::class); 22 | $this->router = \Mockery::spy(\Illuminate\Routing\Router::class); 23 | 24 | $this->config->shouldReceive('get') 25 | ->once() 26 | ->with('larapoke.poking.route') 27 | ->andReturn($route = 'test-poke'); 28 | $this->config->shouldReceive('get') 29 | ->once() 30 | ->with('larapoke.poking.name') 31 | ->andReturn('test-name'); 32 | 33 | $this->config->shouldReceive('get') 34 | ->once() 35 | ->with('larapoke.poking.middleware') 36 | ->andReturn('test-middleware'); 37 | } 38 | 39 | public function testSetGlobalRoute() 40 | { 41 | $this->config->shouldReceive('get') 42 | ->once() 43 | ->with('larapoke.poking.domain') 44 | ->andReturn(null); 45 | 46 | $this->router->shouldReceive('match') 47 | ->once() 48 | ->with('head', 'test-poke') 49 | ->andReturnSelf(); 50 | $this->router->shouldReceive('name') 51 | ->once() 52 | ->with('test-name') 53 | ->andReturnSelf(); 54 | $this->router->shouldReceive('uses') 55 | ->once() 56 | ->with('DarkGhostHunter\Larapoke\Http\Controllers\LarapokeController') 57 | ->andReturnSelf(); 58 | $this->router->shouldReceive('middleware') 59 | ->once() 60 | ->with('test-middleware') 61 | ->andReturnSelf(); 62 | 63 | $generator = new RouteGenerator($this->router, $this->config); 64 | $generator->setRoutes(); 65 | 66 | $this->config->shouldHaveReceived('get') 67 | ->with('larapoke.poking.domain') 68 | ->once(); 69 | 70 | $this->router->shouldHaveReceived('match') 71 | ->with('head', 'test-poke') 72 | ->once(); 73 | $this->router->shouldHaveReceived('name') 74 | ->with('test-name') 75 | ->once(); 76 | $this->router->shouldHaveReceived('uses') 77 | ->with('DarkGhostHunter\Larapoke\Http\Controllers\LarapokeController') 78 | ->once(); 79 | $this->router->shouldHaveReceived('middleware') 80 | ->with('test-middleware') 81 | ->once(); 82 | 83 | } 84 | 85 | public function testSetOneDomainRoute() 86 | { 87 | $this->config->shouldReceive('get') 88 | ->once() 89 | ->with('larapoke.poking.domain') 90 | ->andReturn('one'); 91 | 92 | $this->router->shouldReceive('match') 93 | ->once() 94 | ->with('head', 'test-poke') 95 | ->andReturnSelf(); 96 | $this->router->shouldReceive('uses') 97 | ->once() 98 | ->with('DarkGhostHunter\Larapoke\Http\Controllers\LarapokeController') 99 | ->andReturnSelf(); 100 | $this->router->shouldReceive('middleware') 101 | ->once() 102 | ->with('test-middleware') 103 | ->andReturnSelf(); 104 | $this->router->shouldReceive('name') 105 | ->once() 106 | ->with("one.test-name") 107 | ->andReturnSelf(); 108 | 109 | $generator = new RouteGenerator($this->router, $this->config); 110 | $generator->setRoutes(); 111 | 112 | $this->config->shouldHaveReceived('get') 113 | ->with('larapoke.poking.domain') 114 | ->once(); 115 | 116 | $this->router->shouldHaveReceived('match') 117 | ->with('head', 'test-poke') 118 | ->once(); 119 | $this->router->shouldHaveReceived('uses') 120 | ->with('DarkGhostHunter\Larapoke\Http\Controllers\LarapokeController') 121 | ->once(); 122 | $this->router->shouldHaveReceived('middleware') 123 | ->with('test-middleware') 124 | ->once(); 125 | $this->router->shouldHaveReceived('name') 126 | ->with('one.test-name') 127 | ->once(); 128 | 129 | } 130 | 131 | public function testSetMultipleDomainRoutes() 132 | { 133 | $this->config->shouldReceive('get') 134 | ->once() 135 | ->with('larapoke.poking.domain') 136 | ->andReturn($domains = [ 137 | 'one', 'two', 'three' 138 | ]); 139 | 140 | $this->router->shouldReceive('match') 141 | ->times(count($domains)) 142 | ->with('head', 'test-poke') 143 | ->andReturnSelf(); 144 | $this->router->shouldReceive('uses') 145 | ->times(count($domains)) 146 | ->with('DarkGhostHunter\Larapoke\Http\Controllers\LarapokeController') 147 | ->andReturnSelf(); 148 | $this->router->shouldReceive('middleware') 149 | ->times(count($domains)) 150 | ->with('test-middleware') 151 | ->andReturnSelf(); 152 | 153 | foreach ($domains as $domain) { 154 | $this->router->shouldReceive('name') 155 | ->once() 156 | ->with("$domain.test-name") 157 | ->andReturnSelf(); 158 | } 159 | 160 | $generator = new RouteGenerator($this->router, $this->config); 161 | $generator->setRoutes(); 162 | 163 | $this->config->shouldHaveReceived('get') 164 | ->with('larapoke.poking.domain') 165 | ->once(); 166 | 167 | $this->router->shouldHaveReceived('match') 168 | ->with('head', 'test-poke') 169 | ->times(count($domains)); 170 | $this->router->shouldHaveReceived('uses') 171 | ->with('DarkGhostHunter\Larapoke\Http\Controllers\LarapokeController') 172 | ->times(count($domains)); 173 | $this->router->shouldHaveReceived('middleware') 174 | ->with('test-middleware') 175 | ->times(count($domains)); 176 | 177 | foreach ($domains as $domain) { 178 | $this->router->shouldHaveReceived('name') 179 | ->with("$domain.test-name") 180 | ->once(); 181 | } 182 | } 183 | 184 | protected function tearDown() : void 185 | { 186 | parent::tearDown(); 187 | 188 | \Mockery::close(); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tests/Unit/Modes/ModeBladeTest.php: -------------------------------------------------------------------------------- 1 | scaffoldAuth($app); 18 | 19 | $app->make('config')->set('larapoke.mode', 'blade'); 20 | } 21 | 22 | protected function setUp() : void 23 | { 24 | parent::setUp(); 25 | 26 | /** @var \Illuminate\Routing\Router $router */ 27 | $router = $this->app->make('router'); 28 | 29 | $router->group(['middleware' => ['web']], function () use ($router) { 30 | $router->get('/register', function () { 31 | return $this->app->make(\Illuminate\Contracts\View\Factory::class)->make('auth.register'); 32 | })->name('register'); 33 | $router->get('/login', function () { 34 | return $this->app->make(\Illuminate\Contracts\View\Factory::class)->make('auth.login'); 35 | })->name('login'); 36 | $router->get('/home', function () { 37 | return $this->app->make(\Illuminate\Contracts\View\Factory::class)->make('home'); 38 | })->name('home'); 39 | $router->get('/form-only', function () { 40 | return $this->viewWithFormOnly(); 41 | })->name('form-only'); 42 | $router->get('/multiple-form', function () { 43 | return $this->viewMultipleForms(); 44 | })->name('multiple-form'); 45 | $router->get('/multiple-form-with-middleware', function () { 46 | return $this->viewMultipleForms(); 47 | }) 48 | ->name('multiple-form-with-middleware')->middleware('larapoke'); 49 | $router->get('/not-successful', function () { return new Response('', 400); }); 50 | }); 51 | } 52 | 53 | protected function viewWithFormOnly() 54 | { 55 | /** @var \Illuminate\View\Compilers\BladeCompiler $blade */ 56 | $blade = $this->app->make(\Illuminate\View\Compilers\BladeCompiler::class); 57 | 58 | return $blade->compileString(' 59 | 60 | 61 |
62 | 63 | 64 | 65 |
66 | 67 |
72 |