Skip to content

Commit

Permalink
Merge pull request #73 from buggregator/feature/72
Browse files Browse the repository at this point in the history
Added support for Sentry events from JavaScript SDKs
  • Loading branch information
butschster committed Sep 17, 2023
2 parents 281ee31 + c3bfa04 commit b645a61
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ STORAGE_DEFAULT=default
# Cycle Bridge
CYCLE_SCHEMA_CACHE=true
CYCLE_SCHEMA_WARMUP=false

# Sentry
SENTRY_JS_SDK_URL=https://browser.sentry-cdn.com/7.69.0/bundle.tracing.replay.min.js
SENTRY_DSN_HOST=http://sentry@127.0.0.1:8082
5 changes: 4 additions & 1 deletion app/modules/Sentry/Application/SentryBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Application\Service\HttpHandler\HandlerRegistryInterface;
use Modules\Sentry\EventHandler;
use Modules\Sentry\Interfaces\Http\Handler\EventHandler as HttpEventHandler;
use Modules\Sentry\Interfaces\Http\Handler\JsEventHandler;
use Psr\Container\ContainerInterface;
use Spiral\Boot\Bootloader\Bootloader;

Expand All @@ -18,9 +19,11 @@ final class SentryBootloader extends Bootloader

public function boot(
HandlerRegistryInterface $registry,
HttpEventHandler $handler
HttpEventHandler $handler,
JsEventHandler $jsHandler
): void {
$registry->register($handler);
$registry->register($jsHandler);
}

public function eventHandler(ContainerInterface $container): EventHandlerInterface
Expand Down
78 changes: 78 additions & 0 deletions app/modules/Sentry/Interfaces/Http/Handler/JsEventHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Modules\Sentry\Interfaces\Http\Handler;

use App\Application\Commands\HandleReceivedEvent;
use App\Application\Service\HttpHandler\HandlerInterface;
use Modules\Sentry\Application\EventHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Spiral\Cqrs\CommandBusInterface;
use Spiral\Http\ResponseWrapper;

final class JsEventHandler implements HandlerInterface
{
public function __construct(
private readonly ResponseWrapper $responseWrapper,
private readonly EventHandlerInterface $handler,
private readonly CommandBusInterface $commands,
) {
}

public function priority(): int
{
return 1;
}

public function handle(ServerRequestInterface $request, \Closure $next): ResponseInterface
{
if (!$this->isValidRequest($request)) {
return $next($request);
}

$payloads = \array_map(
static fn(string $payload): array => \json_decode($payload, true, 512, \JSON_THROW_ON_ERROR),
\explode("\n", (string)$request->getBody()),
);

if (!isset($payloads[1]['type'])) {
return $this->responseWrapper->create(404);
}

match ($payloads[1]['type']) {
'event' => $this->handleEvent($payloads),
'transaction' => $this->handleEnvelope($payloads),
default => null,
};

return $this->responseWrapper->create(200);
}

private function handleEvent(array $data): void
{
$event = $this->handler->handle($data[2]);

$this->commands->dispatch(
new HandleReceivedEvent(type: 'sentry', payload: $event),
);
}

/**
* TODO handle sentry transaction and session
*/
private function handleEnvelope(array $data): void
{
if (\count($data) == 3) {
match ($data[1]['type']) {
'transaction' => null,
};
}
}

private function isValidRequest(ServerRequestInterface $request): bool
{
return isset($request->getQueryParams()['sentry_key']);
}
}
31 changes: 31 additions & 0 deletions app/modules/Sentry/Interfaces/Http/JavascriptAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Modules\Sentry\Interfaces\Http;

use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Spiral\Boot\EnvironmentInterface;
use Spiral\Router\Annotation\Route;

final class JavascriptAction
{
#[Route(route: '/sentry/<id>.js', name: 'sentry.js', methods: 'GET', group: 'api')]
public function __invoke(EnvironmentInterface $env, mixed $id): ResponseInterface
{
$jsSdkUrl = $env->get('SENTRY_JS_SDK_URL', 'https://browser.sentry-cdn.com/7.69.0/bundle.tracing.replay.min.js');
$host = $env->get('SENTRY_DSN_HOST', 'http://sentry@127.0.0.1:8000');
$url = \rtrim($host, '/') . '/' . $id;

return new Response(
status: 200,
headers: [
'Content-Type' => 'application/javascript',
],
body: <<<JS
!function(e,n,r,t,i,o,a,c,s){for(var f=s,forceLoad=!1,u=0;u<document.scripts.length;u++)if(document.scripts[u].src.indexOf(o)>-1){f&&"no"===document.scripts[u].getAttribute("data-lazy")&&(f=!1);break}var p=!1,d=[],l=function(e){("e"in e||"p"in e||e.f&&e.f.indexOf("capture")>-1||e.f&&e.f.indexOf("showReportDialog")>-1)&&f&&h(d),l.data.push(e)};function _(){l({e:[].slice.call(arguments)})}function v(e){l({p:"reason"in e?e.reason:"detail"in e&&"reason"in e.detail?e.detail.reason:e})}function h(o){if(!p){p=!0;var s=n.scripts[0],f=n.createElement("script");f.src=a,f.crossOrigin="anonymous",f.addEventListener("load",(function(){try{e.removeEventListener(r,_),e.removeEventListener(t,v),e.SENTRY_SDK_SOURCE="loader";var n=e[i],a=n.init;n.init=function(e){var r=c;for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(r[t]=e[t]);!function(e,n){var r=e.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(e){return e.name}));e.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&r.push(new n.BrowserTracing);(e.replaysSessionSampleRate||e.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&r.push(new n.Replay);e.integrations=r}(r,n),a(r)},function(n,r){try{for(var t=0;t<n.length;t++)"function"==typeof n[t]&&n[t]();var i=l.data,o=!(void 0===(u=e.__SENTRY__)||!u.hub||!u.hub.getClient());i.sort((function(e){return"init"===e.f?-1:0}));var a=!1;for(t=0;t<i.length;t++)if(i[t].f){a=!0;var c=i[t];!1===o&&"init"!==c.f&&r.init(),o=!0,r[c.f].apply(r,c.a)}!1===o&&!1===a&&r.init();var s=e.onerror,f=e.onunhandledrejection;for(t=0;t<i.length;t++)"e"in i[t]&&s?s.apply(e,i[t].e):"p"in i[t]&&f&&f.apply(e,[i[t].p])}catch(e){console.error(e)}var u}(o,n)}catch(e){console.error(e)}})),s.parentNode.insertBefore(f,s)}}l.data=[],e[i]=e[i]||{},e[i].onLoad=function(e){d.push(e),f&&!forceLoad||h(d)},e[i].forceLoad=function(){forceLoad=!0,f&&setTimeout((function(){h(d)}))},["init","addBreadcrumb","captureMessage","captureException","captureEvent","configureScope","withScope","showReportDialog"].forEach((function(n){e[i][n]=function(){l({f:n,a:arguments})}})),e.addEventListener(r,_),e.addEventListener(t,v),f||setTimeout((function(){h(d)}))}(window,document,"error","unhandledrejection","Sentry",'{$id}','{$jsSdkUrl}',{"dsn":"{$url}","tracesSampleRate":1,"replaysSessionSampleRate":0.1,"replaysOnErrorSampleRate":1},false);
JS,
);
}
}
2 changes: 1 addition & 1 deletion app/src/Interfaces/Http/EventHandlerAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class EventHandlerAction
{
public function handle(ServerRequestInterface $request, CoreHandlerInterface $handler): ResponseInterface
{
$auth = (string)$request->getHeaderLine('Authorization');
$auth = $request->getHeaderLine('Authorization');

if (\str_starts_with($auth, 'Basic')) {
$request = $request->withAttribute(
Expand Down

0 comments on commit b645a61

Please sign in to comment.