2024-04-24 12:05:20 +02:00
|
|
|
import { PluginType, patchArrayVectorProrotypeMethods } from '@grafana/data';
|
|
|
|
|
import { config } from '@grafana/runtime';
|
2023-07-20 13:19:57 +02:00
|
|
|
|
2023-08-31 15:45:44 +02:00
|
|
|
import { transformPluginSourceForCDN } from '../cdn/utils';
|
2023-09-27 13:56:35 +02:00
|
|
|
import { resolveWithCache } from '../loader/cache';
|
2024-02-08 12:19:28 +01:00
|
|
|
import { isHostedOnCDN, resolveModulePath } from '../loader/utils';
|
2023-07-20 13:19:57 +02:00
|
|
|
|
2024-04-24 12:05:20 +02:00
|
|
|
import { SandboxEnvironment, SandboxPluginMeta } from './types';
|
2023-07-20 13:19:57 +02:00
|
|
|
|
|
|
|
|
function isSameDomainAsHost(url: string): boolean {
|
|
|
|
|
const locationUrl = new URL(window.location.href);
|
|
|
|
|
const paramUrl = new URL(url);
|
|
|
|
|
return locationUrl.host === paramUrl.host;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-24 12:05:20 +02:00
|
|
|
export async function loadScriptIntoSandbox(url: string, sandboxEnv: SandboxEnvironment) {
|
2023-07-20 13:19:57 +02:00
|
|
|
let scriptCode = '';
|
|
|
|
|
|
|
|
|
|
// same-domain
|
|
|
|
|
if (isSameDomainAsHost(url)) {
|
|
|
|
|
const response = await fetch(url);
|
|
|
|
|
scriptCode = await response.text();
|
2023-10-09 14:23:47 +02:00
|
|
|
//even though this is not loaded via a CDN we need to transform the sourceMapUrl
|
|
|
|
|
scriptCode = transformPluginSourceForCDN({
|
|
|
|
|
url,
|
|
|
|
|
source: scriptCode,
|
|
|
|
|
transformSourceMapURL: true,
|
|
|
|
|
transformAssets: false,
|
|
|
|
|
});
|
2023-07-20 13:19:57 +02:00
|
|
|
// cdn loaded
|
2023-08-31 15:45:44 +02:00
|
|
|
} else if (isHostedOnCDN(url)) {
|
2023-07-20 13:19:57 +02:00
|
|
|
const response = await fetch(url);
|
|
|
|
|
scriptCode = await response.text();
|
|
|
|
|
scriptCode = transformPluginSourceForCDN({
|
2023-08-31 15:45:44 +02:00
|
|
|
url,
|
2023-07-20 13:19:57 +02:00
|
|
|
source: scriptCode,
|
2023-08-31 15:45:44 +02:00
|
|
|
transformSourceMapURL: true,
|
2023-10-09 14:23:47 +02:00
|
|
|
transformAssets: true,
|
2023-07-20 13:19:57 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (scriptCode.length === 0) {
|
|
|
|
|
throw new Error('Only same domain scripts are allowed in sandboxed plugins');
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-09 14:23:47 +02:00
|
|
|
scriptCode = patchPluginAPIs(scriptCode);
|
2023-07-20 13:19:57 +02:00
|
|
|
sandboxEnv.evaluate(scriptCode);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-24 12:05:20 +02:00
|
|
|
export async function getPluginCode(meta: SandboxPluginMeta): Promise<string> {
|
2023-08-31 15:45:44 +02:00
|
|
|
if (isHostedOnCDN(meta.module)) {
|
2023-09-27 13:56:35 +02:00
|
|
|
// Load plugin from CDN, no need for "resolveWithCache" as CDN URLs already include the version
|
2023-08-31 15:45:44 +02:00
|
|
|
const url = meta.module;
|
|
|
|
|
const response = await fetch(url);
|
2023-07-20 13:19:57 +02:00
|
|
|
let pluginCode = await response.text();
|
|
|
|
|
pluginCode = transformPluginSourceForCDN({
|
2023-08-31 15:45:44 +02:00
|
|
|
url,
|
2023-07-20 13:19:57 +02:00
|
|
|
source: pluginCode,
|
2023-08-31 15:45:44 +02:00
|
|
|
transformSourceMapURL: true,
|
2023-10-09 14:23:47 +02:00
|
|
|
transformAssets: true,
|
2023-07-20 13:19:57 +02:00
|
|
|
});
|
|
|
|
|
return pluginCode;
|
|
|
|
|
} else {
|
2024-02-08 12:19:28 +01:00
|
|
|
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);
|
2023-09-27 13:56:35 +02:00
|
|
|
const response = await fetch(pluginCodeUrl);
|
2023-07-20 13:19:57 +02:00
|
|
|
let pluginCode = await response.text();
|
2023-10-09 14:23:47 +02:00
|
|
|
pluginCode = transformPluginSourceForCDN({
|
|
|
|
|
url: pluginCodeUrl,
|
|
|
|
|
source: pluginCode,
|
|
|
|
|
transformSourceMapURL: true,
|
|
|
|
|
transformAssets: false,
|
|
|
|
|
});
|
2023-07-28 14:04:23 +02:00
|
|
|
pluginCode = patchPluginAPIs(pluginCode);
|
2023-07-20 13:19:57 +02:00
|
|
|
return pluginCode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-28 14:04:23 +02:00
|
|
|
function patchPluginAPIs(pluginCode: string): string {
|
|
|
|
|
return pluginCode.replace(/window\.location/gi, 'window.locationSandbox');
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 09:02:55 +02:00
|
|
|
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}()`
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-04-24 12:05:20 +02:00
|
|
|
|
|
|
|
|
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}`);
|
|
|
|
|
}
|