mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 00:41:50 +08:00
Sandbox: Add support for webpack dynamic imports (#71714)
This commit is contained in:
88
public/app/features/plugins/sandbox/code_loader.ts
Normal file
88
public/app/features/plugins/sandbox/code_loader.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { PluginMeta } from '@grafana/data';
|
||||||
|
|
||||||
|
import { getPluginCdnResourceUrl, extractPluginIdVersionFromUrl, transformPluginSourceForCDN } from '../cdn/utils';
|
||||||
|
import { PLUGIN_CDN_URL_KEY } from '../constants';
|
||||||
|
|
||||||
|
import { SandboxEnvironment } from './types';
|
||||||
|
|
||||||
|
function isSameDomainAsHost(url: string): boolean {
|
||||||
|
const locationUrl = new URL(window.location.href);
|
||||||
|
const paramUrl = new URL(url);
|
||||||
|
return locationUrl.host === paramUrl.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadScriptIntoSandbox(url: string, meta: PluginMeta, sandboxEnv: SandboxEnvironment) {
|
||||||
|
let scriptCode = '';
|
||||||
|
|
||||||
|
// same-domain
|
||||||
|
if (isSameDomainAsHost(url)) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
scriptCode = await response.text();
|
||||||
|
scriptCode = patchPluginSourceMap(meta, scriptCode);
|
||||||
|
|
||||||
|
// cdn loaded
|
||||||
|
} else if (url.includes(PLUGIN_CDN_URL_KEY)) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
scriptCode = await response.text();
|
||||||
|
const pluginUrl = getPluginCdnResourceUrl(`/public/${meta.module}`) + '.js';
|
||||||
|
const { version } = extractPluginIdVersionFromUrl(pluginUrl);
|
||||||
|
scriptCode = transformPluginSourceForCDN({
|
||||||
|
pluginId: meta.id,
|
||||||
|
version,
|
||||||
|
source: scriptCode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptCode.length === 0) {
|
||||||
|
throw new Error('Only same domain scripts are allowed in sandboxed plugins');
|
||||||
|
}
|
||||||
|
|
||||||
|
sandboxEnv.evaluate(scriptCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPluginCode(meta: PluginMeta): Promise<string> {
|
||||||
|
if (meta.module.includes(`${PLUGIN_CDN_URL_KEY}/`)) {
|
||||||
|
// should load plugin from a CDN
|
||||||
|
const pluginUrl = getPluginCdnResourceUrl(`/public/${meta.module}`) + '.js';
|
||||||
|
const response = await fetch(pluginUrl);
|
||||||
|
let pluginCode = await response.text();
|
||||||
|
const { version } = extractPluginIdVersionFromUrl(pluginUrl);
|
||||||
|
pluginCode = transformPluginSourceForCDN({
|
||||||
|
pluginId: meta.id,
|
||||||
|
version,
|
||||||
|
source: pluginCode,
|
||||||
|
});
|
||||||
|
return pluginCode;
|
||||||
|
} else {
|
||||||
|
//local plugin loading
|
||||||
|
const response = await fetch('public/' + meta.module + '.js');
|
||||||
|
let pluginCode = await response.text();
|
||||||
|
pluginCode = patchPluginSourceMap(meta, pluginCode);
|
||||||
|
return pluginCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patches the plugin's module.js source code references to sourcemaps to include the full url
|
||||||
|
* of the module.js file instead of the regular relative reference.
|
||||||
|
*
|
||||||
|
* Because the plugin module.js code is loaded via fetch and then "eval" as a string
|
||||||
|
* it can't find the references to the module.js.map directly and we need to patch it
|
||||||
|
* to point to the correct location
|
||||||
|
*/
|
||||||
|
function patchPluginSourceMap(meta: PluginMeta, pluginCode: string): string {
|
||||||
|
// skips inlined and files without source maps
|
||||||
|
if (pluginCode.includes('//# sourceMappingURL=module.js.map')) {
|
||||||
|
let replaceWith = '';
|
||||||
|
// make sure we don't add the sourceURL twice
|
||||||
|
if (!pluginCode.includes('//# sourceURL') || !pluginCode.includes('//@ sourceUrl')) {
|
||||||
|
replaceWith += `//# sourceURL=module.js\n`;
|
||||||
|
}
|
||||||
|
// modify the source map url to point to the correct location
|
||||||
|
const sourceCodeMapUrl = `/public/${meta.module}.js.map`;
|
||||||
|
replaceWith += `//# sourceMappingURL=${sourceCodeMapUrl}`;
|
||||||
|
|
||||||
|
return pluginCode.replace('//# sourceMappingURL=module.js.map', replaceWith);
|
||||||
|
}
|
||||||
|
return pluginCode;
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
export const forbiddenElements = ['script', 'iframe'];
|
export const forbiddenElements = ['iframe'];
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { cloneDeep, isFunction } from 'lodash';
|
import { cloneDeep, isFunction } from 'lodash';
|
||||||
|
|
||||||
|
import { PluginMeta } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
|
import { loadScriptIntoSandbox } from './code_loader';
|
||||||
import { forbiddenElements } from './constants';
|
import { forbiddenElements } from './constants';
|
||||||
|
import { SandboxEnvironment } from './types';
|
||||||
import { logWarning } from './utils';
|
import { logWarning } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,7 +59,10 @@ import { logWarning } from './utils';
|
|||||||
* The code in this file defines that generalDistortionMap.
|
* The code in this file defines that generalDistortionMap.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type DistortionMap = Map<unknown, (originalAttrOrMethod: unknown, pluginId: string) => unknown>;
|
type DistortionMap = Map<
|
||||||
|
unknown,
|
||||||
|
(originalAttrOrMethod: unknown, pluginMeta: PluginMeta, sandboxEnv?: SandboxEnvironment) => unknown
|
||||||
|
>;
|
||||||
const generalDistortionMap: DistortionMap = new Map();
|
const generalDistortionMap: DistortionMap = new Map();
|
||||||
|
|
||||||
const monitorOnly = Boolean(config.featureToggles.frontendSandboxMonitorOnly);
|
const monitorOnly = Boolean(config.featureToggles.frontendSandboxMonitorOnly);
|
||||||
@ -77,9 +83,9 @@ export function getGeneralSandboxDistortionMap() {
|
|||||||
return generalDistortionMap;
|
return generalDistortionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
function failToSet(originalAttrOrMethod: unknown, pluginId: string) {
|
function failToSet(originalAttrOrMethod: unknown, meta: PluginMeta) {
|
||||||
logWarning(`Plugin ${pluginId} tried to set a sandboxed property`, {
|
logWarning(`Plugin ${meta.id} tried to set a sandboxed property`, {
|
||||||
pluginId,
|
pluginId: meta.id,
|
||||||
attrOrMethod: String(originalAttrOrMethod),
|
attrOrMethod: String(originalAttrOrMethod),
|
||||||
entity: 'window',
|
entity: 'window',
|
||||||
});
|
});
|
||||||
@ -98,7 +104,8 @@ function distortIframeAttributes(distortions: DistortionMap) {
|
|||||||
for (const property of iframeHtmlForbiddenProperties) {
|
for (const property of iframeHtmlForbiddenProperties) {
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, property);
|
const descriptor = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, property);
|
||||||
if (descriptor) {
|
if (descriptor) {
|
||||||
function fail(originalAttrOrMethod: unknown, pluginId: string) {
|
function fail(originalAttrOrMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
logWarning(`Plugin ${pluginId} tried to access iframe.${property}`, {
|
logWarning(`Plugin ${pluginId} tried to access iframe.${property}`, {
|
||||||
pluginId,
|
pluginId,
|
||||||
attrOrMethod: property,
|
attrOrMethod: property,
|
||||||
@ -131,7 +138,8 @@ function distortIframeAttributes(distortions: DistortionMap) {
|
|||||||
function distortConsole(distortions: DistortionMap) {
|
function distortConsole(distortions: DistortionMap) {
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(window, 'console');
|
const descriptor = Object.getOwnPropertyDescriptor(window, 'console');
|
||||||
if (descriptor?.value) {
|
if (descriptor?.value) {
|
||||||
function getSandboxConsole(originalAttrOrMethod: unknown, pluginId: string) {
|
function getSandboxConsole(originalAttrOrMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
// we don't monitor the console because we expect a high volume of calls
|
// we don't monitor the console because we expect a high volume of calls
|
||||||
if (monitorOnly) {
|
if (monitorOnly) {
|
||||||
return originalAttrOrMethod;
|
return originalAttrOrMethod;
|
||||||
@ -159,7 +167,8 @@ function distortConsole(distortions: DistortionMap) {
|
|||||||
|
|
||||||
// set distortions to alert to always output to the console
|
// set distortions to alert to always output to the console
|
||||||
function distortAlert(distortions: DistortionMap) {
|
function distortAlert(distortions: DistortionMap) {
|
||||||
function getAlertDistortion(originalAttrOrMethod: unknown, pluginId: string) {
|
function getAlertDistortion(originalAttrOrMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
logWarning(`Plugin ${pluginId} accessed window.alert`, {
|
logWarning(`Plugin ${pluginId} accessed window.alert`, {
|
||||||
pluginId,
|
pluginId,
|
||||||
attrOrMethod: 'alert',
|
attrOrMethod: 'alert',
|
||||||
@ -184,7 +193,8 @@ function distortAlert(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function distortInnerHTML(distortions: DistortionMap) {
|
function distortInnerHTML(distortions: DistortionMap) {
|
||||||
function getInnerHTMLDistortion(originalMethod: unknown, pluginId: string) {
|
function getInnerHTMLDistortion(originalMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
return function innerHTMLDistortion(this: HTMLElement, ...args: string[]) {
|
return function innerHTMLDistortion(this: HTMLElement, ...args: string[]) {
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
const lowerCase = arg?.toLowerCase() || '';
|
const lowerCase = arg?.toLowerCase() || '';
|
||||||
@ -228,7 +238,8 @@ function distortInnerHTML(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function distortCreateElement(distortions: DistortionMap) {
|
function distortCreateElement(distortions: DistortionMap) {
|
||||||
function getCreateElementDistortion(originalMethod: unknown, pluginId: string) {
|
function getCreateElementDistortion(originalMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
return function createElementDistortion(this: HTMLElement, arg?: string, options?: unknown) {
|
return function createElementDistortion(this: HTMLElement, arg?: string, options?: unknown) {
|
||||||
if (arg && forbiddenElements.includes(arg)) {
|
if (arg && forbiddenElements.includes(arg)) {
|
||||||
logWarning(`Plugin ${pluginId} tried to create ${arg}`, {
|
logWarning(`Plugin ${pluginId} tried to create ${arg}`, {
|
||||||
@ -253,7 +264,8 @@ function distortCreateElement(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function distortInsert(distortions: DistortionMap) {
|
function distortInsert(distortions: DistortionMap) {
|
||||||
function getInsertDistortion(originalMethod: unknown, pluginId: string) {
|
function getInsertDistortion(originalMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
return function insertChildDistortion(this: HTMLElement, node?: Node, ref?: Node) {
|
return function insertChildDistortion(this: HTMLElement, node?: Node, ref?: Node) {
|
||||||
const nodeType = node?.nodeName?.toLowerCase() || '';
|
const nodeType = node?.nodeName?.toLowerCase() || '';
|
||||||
|
|
||||||
@ -274,7 +286,8 @@ function distortInsert(distortions: DistortionMap) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getinsertAdjacentElementDistortion(originalMethod: unknown, pluginId: string) {
|
function getinsertAdjacentElementDistortion(originalMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
return function insertAdjacentElementDistortion(this: HTMLElement, position?: string, node?: Node) {
|
return function insertAdjacentElementDistortion(this: HTMLElement, position?: string, node?: Node) {
|
||||||
const nodeType = node?.nodeName?.toLowerCase() || '';
|
const nodeType = node?.nodeName?.toLowerCase() || '';
|
||||||
if (node && forbiddenElements.includes(nodeType)) {
|
if (node && forbiddenElements.includes(nodeType)) {
|
||||||
@ -315,7 +328,8 @@ function distortInsert(distortions: DistortionMap) {
|
|||||||
// set distortions to append elements to the document
|
// set distortions to append elements to the document
|
||||||
function distortAppend(distortions: DistortionMap) {
|
function distortAppend(distortions: DistortionMap) {
|
||||||
// append accepts an array of nodes to append https://developer.mozilla.org/en-US/docs/Web/API/Node/append
|
// append accepts an array of nodes to append https://developer.mozilla.org/en-US/docs/Web/API/Node/append
|
||||||
function getAppendDistortion(originalMethod: unknown, pluginId: string) {
|
function getAppendDistortion(originalMethod: unknown, meta: PluginMeta) {
|
||||||
|
const pluginId = meta.id;
|
||||||
return function appendDistortion(this: HTMLElement, ...args: Node[]) {
|
return function appendDistortion(this: HTMLElement, ...args: Node[]) {
|
||||||
let acceptedNodes = args;
|
let acceptedNodes = args;
|
||||||
const filteredAcceptedNodes = args?.filter((node) => !forbiddenElements.includes(node.nodeName.toLowerCase()));
|
const filteredAcceptedNodes = args?.filter((node) => !forbiddenElements.includes(node.nodeName.toLowerCase()));
|
||||||
@ -341,7 +355,8 @@ function distortAppend(distortions: DistortionMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// appendChild accepts a single node to add https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
|
// appendChild accepts a single node to add https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
|
||||||
function getAppendChildDistortion(originalMethod: unknown, pluginId: string) {
|
function getAppendChildDistortion(originalMethod: unknown, meta: PluginMeta, sandboxEnv?: SandboxEnvironment) {
|
||||||
|
const pluginId = meta.id;
|
||||||
return function appendChildDistortion(this: HTMLElement, arg?: Node) {
|
return function appendChildDistortion(this: HTMLElement, arg?: Node) {
|
||||||
const nodeType = arg?.nodeName?.toLowerCase() || '';
|
const nodeType = arg?.nodeName?.toLowerCase() || '';
|
||||||
if (arg && forbiddenElements.includes(nodeType)) {
|
if (arg && forbiddenElements.includes(nodeType)) {
|
||||||
@ -356,6 +371,19 @@ function distortAppend(distortions: DistortionMap) {
|
|||||||
return document.createDocumentFragment();
|
return document.createDocumentFragment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if the node is a script, load it into the sandbox
|
||||||
|
// this allows webpack chunks to be loaded into the sandbox
|
||||||
|
// loadScriptIntoSandbox has restrictions on what scripts can be loaded
|
||||||
|
if (sandboxEnv && arg && nodeType === 'script' && arg instanceof HTMLScriptElement) {
|
||||||
|
loadScriptIntoSandbox(arg.src, meta, sandboxEnv)
|
||||||
|
.then(() => {
|
||||||
|
arg.onload?.call(arg, new Event('load'));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
arg.onerror?.call(arg, new ErrorEvent('error', { error: err }));
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
if (isFunction(originalMethod)) {
|
if (isFunction(originalMethod)) {
|
||||||
return originalMethod.call(this, arg);
|
return originalMethod.call(this, arg);
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,9 @@ import { ProxyTarget } from '@locker/near-membrane-shared';
|
|||||||
|
|
||||||
import { PluginMeta } from '@grafana/data';
|
import { PluginMeta } from '@grafana/data';
|
||||||
|
|
||||||
import { extractPluginIdVersionFromUrl, getPluginCdnResourceUrl, transformPluginSourceForCDN } from '../cdn/utils';
|
|
||||||
import { PLUGIN_CDN_URL_KEY } from '../constants';
|
|
||||||
import { getPluginSettings } from '../pluginSettings';
|
import { getPluginSettings } from '../pluginSettings';
|
||||||
|
|
||||||
|
import { getPluginCode } from './code_loader';
|
||||||
import { getGeneralSandboxDistortionMap } from './distortion_map';
|
import { getGeneralSandboxDistortionMap } from './distortion_map';
|
||||||
import {
|
import {
|
||||||
getSafeSandboxDomElement,
|
getSafeSandboxDomElement,
|
||||||
@ -17,7 +16,7 @@ import {
|
|||||||
} from './document_sandbox';
|
} from './document_sandbox';
|
||||||
import { sandboxPluginDependencies } from './plugin_dependencies';
|
import { sandboxPluginDependencies } from './plugin_dependencies';
|
||||||
import { sandboxPluginComponents } from './sandbox_components';
|
import { sandboxPluginComponents } from './sandbox_components';
|
||||||
import { CompartmentDependencyModule, PluginFactoryFunction } from './types';
|
import { CompartmentDependencyModule, PluginFactoryFunction, SandboxEnvironment } from './types';
|
||||||
import { logError } from './utils';
|
import { logError } from './utils';
|
||||||
|
|
||||||
// Loads near membrane custom formatter for near membrane proxy objects.
|
// Loads near membrane custom formatter for near membrane proxy objects.
|
||||||
@ -45,31 +44,30 @@ export async function importPluginModuleInSandbox({ pluginId }: { pluginId: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise<unknown> {
|
async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise<unknown> {
|
||||||
const generalDistortionMap = getGeneralSandboxDistortionMap();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* this function is executed every time a plugin calls any DOM API
|
|
||||||
* it must be kept as lean and performant as possible and sync
|
|
||||||
*/
|
|
||||||
function distortionCallback(originalValue: ProxyTarget): ProxyTarget {
|
|
||||||
if (isDomElement(originalValue)) {
|
|
||||||
const element = getSafeSandboxDomElement(originalValue, meta.id);
|
|
||||||
// the element.style attribute should be a live target to work in chrome
|
|
||||||
markDomElementStyleAsALiveTarget(element);
|
|
||||||
return element;
|
|
||||||
} else {
|
|
||||||
patchObjectAsLiveTarget(originalValue);
|
|
||||||
}
|
|
||||||
const distortion = generalDistortionMap.get(originalValue);
|
|
||||||
if (distortion) {
|
|
||||||
return distortion(originalValue, meta.id) as ProxyTarget;
|
|
||||||
}
|
|
||||||
return originalValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const generalDistortionMap = getGeneralSandboxDistortionMap();
|
||||||
|
let sandboxEnvironment: SandboxEnvironment;
|
||||||
|
/*
|
||||||
|
* this function is executed every time a plugin calls any DOM API
|
||||||
|
* it must be kept as lean and performant as possible and sync
|
||||||
|
*/
|
||||||
|
function distortionCallback(originalValue: ProxyTarget): ProxyTarget {
|
||||||
|
if (isDomElement(originalValue)) {
|
||||||
|
const element = getSafeSandboxDomElement(originalValue, meta.id);
|
||||||
|
// the element.style attribute should be a live target to work in chrome
|
||||||
|
markDomElementStyleAsALiveTarget(element);
|
||||||
|
return element;
|
||||||
|
} else {
|
||||||
|
patchObjectAsLiveTarget(originalValue);
|
||||||
|
}
|
||||||
|
const distortion = generalDistortionMap.get(originalValue);
|
||||||
|
if (distortion) {
|
||||||
|
return distortion(originalValue, meta, sandboxEnvironment) as ProxyTarget;
|
||||||
|
}
|
||||||
|
return originalValue;
|
||||||
|
}
|
||||||
// each plugin has its own sandbox
|
// each plugin has its own sandbox
|
||||||
const sandboxEnvironment = createVirtualEnvironment(window, {
|
sandboxEnvironment = createVirtualEnvironment(window, {
|
||||||
// distortions are interceptors to modify the behavior of objects when
|
// distortions are interceptors to modify the behavior of objects when
|
||||||
// the code inside the sandbox tries to access them
|
// the code inside the sandbox tries to access them
|
||||||
distortionCallback,
|
distortionCallback,
|
||||||
@ -158,28 +156,6 @@ async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise<unknown>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPluginCode(meta: PluginMeta): Promise<string> {
|
|
||||||
if (meta.module.includes(`${PLUGIN_CDN_URL_KEY}/`)) {
|
|
||||||
// should load plugin from a CDN
|
|
||||||
const pluginUrl = getPluginCdnResourceUrl(`/public/${meta.module}`) + '.js';
|
|
||||||
const response = await fetch(pluginUrl);
|
|
||||||
let pluginCode = await response.text();
|
|
||||||
const { version } = extractPluginIdVersionFromUrl(pluginUrl);
|
|
||||||
pluginCode = transformPluginSourceForCDN({
|
|
||||||
pluginId: meta.id,
|
|
||||||
version,
|
|
||||||
source: pluginCode,
|
|
||||||
});
|
|
||||||
return pluginCode;
|
|
||||||
} else {
|
|
||||||
//local plugin loading
|
|
||||||
const response = await fetch('public/' + meta.module + '.js');
|
|
||||||
let pluginCode = await response.text();
|
|
||||||
pluginCode = patchPluginSourceMap(meta, pluginCode);
|
|
||||||
return pluginCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActivityErrorHandler(pluginId: string) {
|
function getActivityErrorHandler(pluginId: string) {
|
||||||
return async function error(proxyError?: Error & { sandboxError?: boolean }) {
|
return async function error(proxyError?: Error & { sandboxError?: boolean }) {
|
||||||
if (!proxyError) {
|
if (!proxyError) {
|
||||||
@ -203,31 +179,6 @@ function getActivityErrorHandler(pluginId: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Patches the plugin's module.js source code references to sourcemaps to include the full url
|
|
||||||
* of the module.js file instead of the regular relative reference.
|
|
||||||
*
|
|
||||||
* Because the plugin module.js code is loaded via fetch and then "eval" as a string
|
|
||||||
* it can't find the references to the module.js.map directly and we need to patch it
|
|
||||||
* to point to the correct location
|
|
||||||
*/
|
|
||||||
function patchPluginSourceMap(meta: PluginMeta, pluginCode: string): string {
|
|
||||||
// skips inlined and files without source maps
|
|
||||||
if (pluginCode.includes('//# sourceMappingURL=module.js.map')) {
|
|
||||||
let replaceWith = '';
|
|
||||||
// make sure we don't add the sourceURL twice
|
|
||||||
if (!pluginCode.includes('//# sourceURL') || !pluginCode.includes('//@ sourceUrl')) {
|
|
||||||
replaceWith += `//# sourceURL=module.js\n`;
|
|
||||||
}
|
|
||||||
// modify the source map url to point to the correct location
|
|
||||||
const sourceCodeMapUrl = `/public/${meta.module}.js.map`;
|
|
||||||
replaceWith += `//# sourceMappingURL=${sourceCodeMapUrl}`;
|
|
||||||
|
|
||||||
return pluginCode.replace('//# sourceMappingURL=module.js.map', replaceWith);
|
|
||||||
}
|
|
||||||
return pluginCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePluginDependencies(deps: string[]) {
|
function resolvePluginDependencies(deps: string[]) {
|
||||||
// resolve dependencies
|
// resolve dependencies
|
||||||
const resolvedDeps: CompartmentDependencyModule[] = [];
|
const resolvedDeps: CompartmentDependencyModule[] = [];
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import createVirtualEnvironment from '@locker/near-membrane-dom';
|
||||||
|
|
||||||
import { GrafanaPlugin } from '@grafana/data';
|
import { GrafanaPlugin } from '@grafana/data';
|
||||||
|
|
||||||
export type CompartmentDependencyModule = unknown;
|
export type CompartmentDependencyModule = unknown;
|
||||||
@ -6,3 +8,5 @@ export type PluginFactoryFunction = (...args: CompartmentDependencyModule[]) =>
|
|||||||
export type SandboxedPluginObject = {
|
export type SandboxedPluginObject = {
|
||||||
plugin: GrafanaPlugin | Promise<GrafanaPlugin>;
|
plugin: GrafanaPlugin | Promise<GrafanaPlugin>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SandboxEnvironment = ReturnType<typeof createVirtualEnvironment>;
|
||||||
|
Reference in New Issue
Block a user