Frontend o11y: Load SharedWorkers for crash detection (#96673)

* Load SharedWorkers from blob

* Fix typo

* Update docs

* Add more docs

* Simplify extending CorsSharedWorker

* Revert "Simplify extending CorsSharedWorker"

This reverts commit 1603e5f02f.

* Simplify extending CorsSharedWorker

* Remove ts-ignore

* Update betterer and add docs

* Update public/app/core/crash/index.ts

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update public/app/core/utils/CorsSharedWorker.ts

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update public/app/core/crash/index.ts

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update public/app/core/utils/CorsSharedWorker.ts

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Simplify getting scriptsBasePathUrl

* Disable linting for SharedWorker type assertion

---------

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
This commit is contained in:
Piotr Jamróz 2024-12-02 15:12:36 +01:00 committed by GitHub
parent 5f1fae8efd
commit 60f9ff0a07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 5 deletions

View File

@ -3,8 +3,10 @@ import { BaseStateReport } from 'crashme/dist/types';
import { nanoid } from 'nanoid';
import { config, createMonitoringLogger } from '@grafana/runtime';
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { contextSrv } from '../services/context_srv';
import { CorsSharedWorker as SharedWorker, sharedWorkersSupported } from '../utils/CorsSharedWorker';
import { isChromePerformance, prepareContext } from './crash.utils';
@ -30,6 +32,10 @@ interface GrafanaCrashReport extends BaseStateReport {
}
export function initializeCrashDetection() {
if (!sharedWorkersSupported()) {
return;
}
initCrashDetection<GrafanaCrashReport>({
id: nanoid(5),
@ -39,8 +45,18 @@ export function initializeCrashDetection() {
return new Worker(new URL('./client.worker', import.meta.url));
},
createDetectorWorker(): SharedWorker {
return new SharedWorker(new URL('./detector.worker', import.meta.url));
/**
* There are limitations that require us to manually assert the type here.
* 1) Webpack uses static code analysis to create a new entry point for a SharedWorker.
* It requies constructing an object with exact syntax new SharedWorker(...) (https://webpack.js.org/guides/web-workers/)
* 2) Some browsers may not support SharedWorkers hence we cannot extend CorsSharedWorker like CorsWorker and
* window.SharedWorker needs to be referenced during runtime only if it is supported (https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker)
*
* We guarantee the type assertion is correct by returning a SharedWorker in CorsSharedWorker constructor.
*/
createDetectorWorker() {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return new SharedWorker(new URL('./detector.worker', import.meta.url)) as globalThis.SharedWorker;
},
reportCrash: async (report) => {

View File

@ -0,0 +1,33 @@
// Almost identical to CorsWorker.ts. Main difference being it allows loading a SharedWorker if browser supports it
export function sharedWorkersSupported() {
return typeof window.SharedWorker !== 'undefined';
}
/**
* Creating CorsSharedWorker should be called only if sharedWorkersSupported() is truthy
*/
export class CorsSharedWorker {
constructor(url: URL, options?: WorkerOptions) {
if (!sharedWorkersSupported()) {
throw new Error('SharedWorker is not supported');
}
// by default, worker inherits HTML document's location and pathname which leads to wrong public path value
// the CorsWorkerPlugin will override it with the value based on the initial worker chunk, ie.
// initial worker chunk: http://host.com/cdn/scripts/worker-123.js
// resulting public path: http://host.com/cdn/scripts
const scriptUrl = url.toString();
const scriptsBasePathUrl = new URL('.', url).toString();
const importScripts = `importScripts('${scriptUrl}');`;
const objectURL = URL.createObjectURL(
new Blob([`__webpack_worker_public_path__ = '${scriptsBasePathUrl}'; ${importScripts}`], {
type: 'application/javascript',
})
);
const worker = new SharedWorker(objectURL, options);
URL.revokeObjectURL(objectURL);
return worker;
}
}

View File

@ -7,9 +7,7 @@ export class CorsWorker extends window.Worker {
// resulting public path: http://host.com/cdn/scripts
const scriptUrl = url.toString();
const urlParts = scriptUrl.split('/');
urlParts.pop();
const scriptsBasePathUrl = `${urlParts.join('/')}/`;
const scriptsBasePathUrl = new URL('.', url).toString();
const importScripts = `importScripts('${scriptUrl}');`;
const objectURL = URL.createObjectURL(