Files
grafana/public/app/features/plugins/sandbox/code_loader.ts
Esteban Beltran 1e1c62fef1 FE Sandbox: Get plugin module path from bootdata instead of plugin settings (#86702)
* FE Sandbox: Get plugin module path from bootdata instead of plugin settings

* Remove unnecessary async/await
2024-04-24 12:05:20 +02:00

122 lines
3.8 KiB
TypeScript

import { PluginType, patchArrayVectorProrotypeMethods } from '@grafana/data';
import { config } from '@grafana/runtime';
import { transformPluginSourceForCDN } from '../cdn/utils';
import { resolveWithCache } from '../loader/cache';
import { isHostedOnCDN, resolveModulePath } from '../loader/utils';
import { SandboxEnvironment, SandboxPluginMeta } 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, sandboxEnv: SandboxEnvironment) {
let scriptCode = '';
// same-domain
if (isSameDomainAsHost(url)) {
const response = await fetch(url);
scriptCode = await response.text();
//even though this is not loaded via a CDN we need to transform the sourceMapUrl
scriptCode = transformPluginSourceForCDN({
url,
source: scriptCode,
transformSourceMapURL: true,
transformAssets: false,
});
// cdn loaded
} else if (isHostedOnCDN(url)) {
const response = await fetch(url);
scriptCode = await response.text();
scriptCode = transformPluginSourceForCDN({
url,
source: scriptCode,
transformSourceMapURL: true,
transformAssets: true,
});
}
if (scriptCode.length === 0) {
throw new Error('Only same domain scripts are allowed in sandboxed plugins');
}
scriptCode = patchPluginAPIs(scriptCode);
sandboxEnv.evaluate(scriptCode);
}
export async function getPluginCode(meta: SandboxPluginMeta): Promise<string> {
if (isHostedOnCDN(meta.module)) {
// Load plugin from CDN, no need for "resolveWithCache" as CDN URLs already include the version
const url = meta.module;
const response = await fetch(url);
let pluginCode = await response.text();
pluginCode = transformPluginSourceForCDN({
url,
source: pluginCode,
transformSourceMapURL: true,
transformAssets: true,
});
return pluginCode;
} else {
let modulePath = resolveModulePath(meta.module);
// resolveWithCache will append a query parameter with its version
// to ensure correct cached version is served for local plugins
const pluginCodeUrl = resolveWithCache(modulePath);
const response = await fetch(pluginCodeUrl);
let pluginCode = await response.text();
pluginCode = transformPluginSourceForCDN({
url: pluginCodeUrl,
source: pluginCode,
transformSourceMapURL: true,
transformAssets: false,
});
pluginCode = patchPluginAPIs(pluginCode);
return pluginCode;
}
}
function patchPluginAPIs(pluginCode: string): string {
return pluginCode.replace(/window\.location/gi, 'window.locationSandbox');
}
export function patchSandboxEnvironmentPrototype(sandboxEnvironment: SandboxEnvironment) {
// same as https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/vector.ts#L16
// Array is a "reflective" type in Near-membrane and doesn't get an identify continuity
sandboxEnvironment.evaluate(
`${patchArrayVectorProrotypeMethods.toString()};${patchArrayVectorProrotypeMethods.name}()`
);
}
export function getPluginLoadData(pluginId: string): SandboxPluginMeta {
// find it in datasources
for (const datasource of Object.values(config.datasources)) {
if (datasource.type === pluginId) {
return datasource.meta;
}
}
//find it in panels
for (const panel of Object.values(config.panels)) {
if (panel.id === pluginId) {
return panel;
}
}
//find it in apps
//the information inside the apps object is more limited
for (const app of Object.values(config.apps)) {
if (app.id === pluginId) {
return {
id: pluginId,
type: PluginType.app,
module: app.path,
};
}
}
throw new Error(`Could not find plugin ${pluginId}`);
}